From d9acce34b7a5784ec324b0026e82f0ae589ee039 Mon Sep 17 00:00:00 2001 From: Takayuki Maeda Date: Thu, 29 Jun 2017 21:26:12 +0900 Subject: [PATCH 01/65] Use stringify to generate a query string --- docs/Tutorial.md | 8 ++++---- src/rest/jsonServer.js | 7 ++++--- src/rest/simple.js | 9 +++++---- src/util/fetch.js | 5 ++--- src/util/fetch.spec.js | 25 +++++++++++++++++++++++++ 5 files changed, 40 insertions(+), 14 deletions(-) create mode 100644 src/util/fetch.spec.js diff --git a/docs/Tutorial.md b/docs/Tutorial.md index d12c5d5e..b302ddfc 100644 --- a/docs/Tutorial.md +++ b/docs/Tutorial.md @@ -574,6 +574,7 @@ import { DELETE, fetchUtils, } from 'admin-on-rest'; +import { stringify } from 'query-string'; const API_URL = 'my.api.url'; @@ -585,7 +586,6 @@ const API_URL = 'my.api.url'; */ const convertRESTRequestToHTTP = (type, resource, params) => { let url = ''; - const { queryParameters } = fetchUtils; const options = {}; switch (type) { case GET_LIST: { @@ -596,7 +596,7 @@ const convertRESTRequestToHTTP = (type, resource, params) => { range: JSON.stringify([(page - 1) * perPage, page * perPage - 1]), filter: JSON.stringify(params.filter), }; - url = `${API_URL}/${resource}?${queryParameters(query)}`; + url = `${API_URL}/${resource}?${stringify(query)}`; break; } case GET_ONE: @@ -606,7 +606,7 @@ const convertRESTRequestToHTTP = (type, resource, params) => { const query = { filter: JSON.stringify({ id: params.ids }), }; - url = `${API_URL}/${resource}?${queryParameters(query)}`; + url = `${API_URL}/${resource}?${stringify(query)}`; break; } case GET_MANY_REFERENCE: { @@ -617,7 +617,7 @@ const convertRESTRequestToHTTP = (type, resource, params) => { range: JSON.stringify([(page - 1) * perPage, (page * perPage) - 1]), filter: JSON.stringify({ ...params.filter, [params.target]: params.id }), }; - url = `${API_URL}/${resource}?${queryParameters(query)}`; + url = `${API_URL}/${resource}?${stringify(query)}`; break; } case UPDATE: diff --git a/src/rest/jsonServer.js b/src/rest/jsonServer.js index 4246b79d..f99ed3ce 100644 --- a/src/rest/jsonServer.js +++ b/src/rest/jsonServer.js @@ -1,4 +1,5 @@ -import { queryParameters, fetchJson } from '../util/fetch'; +import { stringify } from 'query-string'; +import { fetchJson } from '../util/fetch'; import { GET_LIST, GET_ONE, @@ -42,7 +43,7 @@ export default (apiUrl, httpClient = fetchJson) => { _start: (page - 1) * perPage, _end: page * perPage, }; - url = `${apiUrl}/${resource}?${queryParameters(query)}`; + url = `${apiUrl}/${resource}?${stringify(query)}`; break; } case GET_ONE: @@ -59,7 +60,7 @@ export default (apiUrl, httpClient = fetchJson) => { _start: (page - 1) * perPage, _end: page * perPage, }; - url = `${apiUrl}/${resource}?${queryParameters(query)}`; + url = `${apiUrl}/${resource}?${stringify(query)}`; break; } case UPDATE: diff --git a/src/rest/simple.js b/src/rest/simple.js index e51739e8..c9bdc729 100644 --- a/src/rest/simple.js +++ b/src/rest/simple.js @@ -1,4 +1,5 @@ -import { queryParameters, fetchJson } from '../util/fetch'; +import { stringify } from 'query-string'; +import { fetchJson } from '../util/fetch'; import { GET_LIST, GET_ONE, @@ -41,7 +42,7 @@ export default (apiUrl, httpClient = fetchJson) => { range: JSON.stringify([(page - 1) * perPage, (page * perPage) - 1]), filter: JSON.stringify(params.filter), }; - url = `${apiUrl}/${resource}?${queryParameters(query)}`; + url = `${apiUrl}/${resource}?${stringify(query)}`; break; } case GET_ONE: @@ -51,7 +52,7 @@ export default (apiUrl, httpClient = fetchJson) => { const query = { filter: JSON.stringify({ id: params.ids }), }; - url = `${apiUrl}/${resource}?${queryParameters(query)}`; + url = `${apiUrl}/${resource}?${stringify(query)}`; break; } case GET_MANY_REFERENCE: { @@ -62,7 +63,7 @@ export default (apiUrl, httpClient = fetchJson) => { range: JSON.stringify([(page - 1) * perPage, (page * perPage) - 1]), filter: JSON.stringify({ ...params.filter, [params.target]: params.id }), }; - url = `${apiUrl}/${resource}?${queryParameters(query)}`; + url = `${apiUrl}/${resource}?${stringify(query)}`; break; } case UPDATE: diff --git a/src/util/fetch.js b/src/util/fetch.js index b6e6be99..e36051cc 100644 --- a/src/util/fetch.js +++ b/src/util/fetch.js @@ -1,4 +1,5 @@ import HttpError from './HttpError'; +import { stringify } from 'query-string'; export const fetchJson = (url, options = {}) => { const requestHeaders = options.headers || new Headers({ @@ -32,6 +33,4 @@ export const fetchJson = (url, options = {}) => { }); }; -export const queryParameters = data => Object.keys(data) - .map(key => [key, data[key]].map(encodeURIComponent).join('=')) - .join('&'); +export const queryParameters = stringify; diff --git a/src/util/fetch.spec.js b/src/util/fetch.spec.js new file mode 100644 index 00000000..59b10d6b --- /dev/null +++ b/src/util/fetch.spec.js @@ -0,0 +1,25 @@ +import assert from 'assert'; +import { queryParameters } from './fetch'; + +describe('queryParameters', () => { + it('should generate a query parameter', () => { + const data = { foo: 'bar' }; + assert.equal(queryParameters(data), 'foo=bar'); + }); + + it('should generate multiple query parameters', () => { + const data = { foo: 'fooval', bar: 'barval' }; + const actual = queryParameters(data); + assert(['foo=fooval&bar=barval', 'bar=barval&foo=fooval'].includes(actual)); + }); + + it('should generate multiple query parameters with a same name', () => { + const data = { foo: ['bar', 'baz'] }; + assert.equal(queryParameters(data), 'foo=bar&foo=baz'); + }); + + it('should generate an encoded query parameter', () => { + const data = ['foo=bar', 'foo?bar&baz']; + assert.equal(queryParameters({ [data[0]]: data[1] }), data.map(encodeURIComponent).join('=')); + }); +}); From fc1524e80e4588539287cbd572adc3b44c5c877b Mon Sep 17 00:00:00 2001 From: Francois Zaninotto Date: Mon, 3 Jul 2017 11:26:13 +0200 Subject: [PATCH 02/65] Add explicit error message when decorating aor Input components --- src/mui/input/AutocompleteInput.js | 6 +++- src/mui/input/DateInput.js | 6 +++- src/mui/input/Labeled.js | 47 +++++++++++++++--------------- src/mui/input/LongTextInput.js | 29 +++++++++++------- src/mui/input/NumberInput.js | 7 ++++- src/mui/input/SelectArrayInput.js | 6 +++- src/mui/input/SelectInput.js | 7 ++++- src/mui/input/TextInput.js | 6 +++- 8 files changed, 73 insertions(+), 41 deletions(-) diff --git a/src/mui/input/AutocompleteInput.js b/src/mui/input/AutocompleteInput.js index 83b69937..16dd3d0a 100644 --- a/src/mui/input/AutocompleteInput.js +++ b/src/mui/input/AutocompleteInput.js @@ -94,13 +94,17 @@ export class AutocompleteInput extends Component { input, isRequired, label, - meta: { touched, error }, + meta, options, optionValue, resource, setFilter, source, } = this.props; + if (typeof meta === 'undefined') { + throw new Error('The AutocompleteInput component wasn\'t called within a redux-form . Did you decorate it and forget to add the addField prop to your component? See https://marmelab.com/admin-on-rest/Inputs.html#writing-your-own-input-component for details.'); + } + const { touched, error } = meta; const selectedSource = choices.find(choice => get(choice, optionValue) === input.value); const dataSource = choices.map(choice => ({ diff --git a/src/mui/input/DateInput.js b/src/mui/input/DateInput.js index a157acc1..f9fe2d71 100644 --- a/src/mui/input/DateInput.js +++ b/src/mui/input/DateInput.js @@ -35,7 +35,11 @@ class DateInput extends Component { onDismiss = () => this.props.input.onBlur(); render() { - const { input, isRequired, label, meta: { touched, error }, options, source, elStyle, resource } = this.props; + const { input, isRequired, label, meta, options, source, elStyle, resource } = this.props; + if (typeof meta === 'undefined') { + throw new Error('The DateInput component wasn\'t called within a redux-form . Did you decorate it and forget to add the addField prop to your component? See https://marmelab.com/admin-on-rest/Inputs.html#writing-your-own-input-component for details.'); + } + const { touched, error } = meta; return ( * */ -class Labeled extends Component { - render() { - const { input, isRequired, meta, label, resource, record, onChange, basePath, children, source, disabled = true, labelStyle = defaultLabelStyle } = this.props; - if (!label && !source) { - throw new Error(`Cannot create label for component <${children && children.type && children.type.name}>: You must set either the label or source props. You can also disable automated label insertion by setting 'addLabel: false' in the component default props`); - } - return ( - } - floatingLabelFixed - fullWidth - disabled={disabled} - underlineShow={false} - style={labelStyle} - errorText={meta && meta.touched && meta.error} - > - {children && typeof children.type !== 'string' ? - React.cloneElement(children, { input, meta, record, resource, onChange, basePath }) : - children - } - - ); +const Labeled = () => { + const { input, isRequired, label, meta, resource, children, source, disabled = true, labelStyle = defaultLabelStyle, ...rest } = this.props; + + if (!label && !source) { + throw new Error(`Cannot create label for component <${children && children.type && children.type.name}>: You must set either the label or source props. You can also disable automated label insertion by setting 'addLabel: false' in the component default props`); } -} + return ( + } + floatingLabelFixed + fullWidth + disabled={disabled} + underlineShow={false} + style={labelStyle} + errorText={meta && meta.touched && meta.error} + > + {children && typeof children.type !== 'string' ? + React.cloneElement(children, { input, meta, resource, ...rest }) : + children + } + + ); +}; Labeled.propTypes = { basePath: PropTypes.string, diff --git a/src/mui/input/LongTextInput.js b/src/mui/input/LongTextInput.js index dcbe8d85..930899a7 100644 --- a/src/mui/input/LongTextInput.js +++ b/src/mui/input/LongTextInput.js @@ -3,17 +3,24 @@ import PropTypes from 'prop-types'; import TextField from 'material-ui/TextField'; import FieldTitle from '../../util/FieldTitle'; -const LongTextInput = ({ input, isRequired, label, meta: { touched, error }, options, source, elStyle, resource }) => ( - } - errorText={touched && error} - style={elStyle} - {...options} - /> -); +const LongTextInput = ({ input, isRequired, label, meta, options, source, elStyle, resource }) => { + if (typeof meta === 'undefined') { + throw new Error('The LongTextInput component wasn\'t called within a redux-form . Did you decorate it and forget to add the addField prop to your component? See https://marmelab.com/admin-on-rest/Inputs.html#writing-your-own-input-component for details.'); + } + const { touched, error } = meta; + + return ( + } + errorText={touched && error} + style={elStyle} + {...options} + /> + ); +} LongTextInput.propTypes = { addField: PropTypes.bool.isRequired, diff --git a/src/mui/input/NumberInput.js b/src/mui/input/NumberInput.js index 643264f0..a245b0fc 100644 --- a/src/mui/input/NumberInput.js +++ b/src/mui/input/NumberInput.js @@ -39,7 +39,12 @@ class NumberInput extends Component { } render() { - const { elStyle, input, isRequired, label, meta: { touched, error }, options, source, step, resource } = this.props; + const { elStyle, input, isRequired, label, meta, options, source, step, resource } = this.props; + if (typeof meta === 'undefined') { + throw new Error('The NumberInput component wasn\'t called within a redux-form . Did you decorate it and forget to add the addField prop to your component? See https://marmelab.com/admin-on-rest/Inputs.html#writing-your-own-input-component for details.'); + } + const { touched, error } = meta; + return ( . Did you decorate it and forget to add the addField prop to your component? See https://marmelab.com/admin-on-rest/Inputs.html#writing-your-own-input-component for details.'); + } + const { touched, error } = meta; return ( . Did you decorate it and forget to add the addField prop to your component? See https://marmelab.com/admin-on-rest/Inputs.html#writing-your-own-input-component for details.'); + } + const { touched, error } = meta; + return ( . Did you decorate it and forget to add the addField prop to your component? See https://marmelab.com/admin-on-rest/Inputs.html#writing-your-own-input-component for details.'); + } + const { touched, error } = meta; return ( Date: Mon, 3 Jul 2017 14:47:02 +0200 Subject: [PATCH 03/65] Use prettier for code formatting --- .eslintrc | 50 +- example/addUploadFeature.js | 50 +- example/app.js | 46 +- example/authClient.js | 8 +- example/comments.js | 140 +- example/data.js | 117 +- example/posts.js | 163 +- example/webpack.config.js | 2 +- package-lock.json | 8090 +++++++++++++++++ package.json | 4 +- src/Admin.js | 74 +- src/AdminRoutes.js | 71 +- src/AdminRoutes.spec.js | 30 +- src/CrudRoute.js | 35 +- src/Resource.js | 11 +- src/actions/dataActions.js | 60 +- src/auth/Restricted.js | 2 +- src/auth/Restricted.spec.js | 20 +- src/i18n/TranslationProvider.js | 35 +- src/i18n/TranslationUtils.js | 3 +- src/i18n/TranslationUtils.spec.js | 2 +- src/i18n/messages.js | 9 +- src/i18n/translate.js | 2 +- src/mui/auth/Login.js | 64 +- src/mui/auth/Logout.js | 7 +- src/mui/button/CreateButton.js | 37 +- src/mui/button/DeleteButton.js | 22 +- src/mui/button/EditButton.js | 32 +- src/mui/button/ListButton.js | 15 +- src/mui/button/RefreshButton.js | 13 +- src/mui/button/SaveButton.js | 30 +- src/mui/button/SaveButton.spec.js | 54 +- src/mui/button/ShowButton.js | 34 +- src/mui/delete/Delete.js | 48 +- src/mui/detail/Create.js | 43 +- src/mui/detail/CreateActions.js | 5 +- src/mui/detail/Edit.js | 90 +- src/mui/detail/Edit.spec.js | 22 +- src/mui/detail/EditActions.js | 5 +- src/mui/detail/Show.js | 63 +- src/mui/detail/Show.spec.js | 22 +- src/mui/detail/ShowActions.js | 5 +- src/mui/detail/SimpleShowLayout.js | 46 +- src/mui/detail/Tab.js | 40 +- src/mui/detail/TabbedShowLayout.js | 40 +- src/mui/field/BooleanField.spec.js | 61 +- src/mui/field/ChipField.js | 4 +- src/mui/field/DateField.js | 29 +- src/mui/field/DateField.spec.js | 115 +- src/mui/field/EmailField.js | 4 +- src/mui/field/EmailField.spec.js | 19 +- src/mui/field/FileField.js | 11 +- src/mui/field/FileField.spec.js | 33 +- src/mui/field/FunctionField.js | 9 +- src/mui/field/FunctionField.spec.js | 8 +- src/mui/field/ImageField.spec.js | 38 +- src/mui/field/NumberField.js | 19 +- src/mui/field/NumberField.spec.js | 82 +- src/mui/field/ReferenceArrayField.js | 16 +- src/mui/field/ReferenceField.js | 38 +- src/mui/field/ReferenceField.spec.js | 10 +- src/mui/field/ReferenceManyField.js | 56 +- src/mui/field/ReferenceManyField.spec.js | 63 +- src/mui/field/RichTextField.js | 11 +- src/mui/field/RichTextField.spec.js | 35 +- src/mui/field/SelectField.js | 31 +- src/mui/field/SelectField.spec.js | 159 +- src/mui/field/TextField.spec.js | 22 +- src/mui/field/UrlField.js | 5 +- src/mui/field/UrlField.spec.js | 18 +- src/mui/form/FormField.js | 8 +- src/mui/form/FormField.spec.js | 6 +- src/mui/form/FormTab.js | 22 +- src/mui/form/SimpleForm.js | 58 +- src/mui/form/SimpleForm.spec.js | 5 +- src/mui/form/TabbedForm.js | 90 +- src/mui/form/TabbedForm.spec.js | 61 +- src/mui/form/getDefaultValues.js | 20 +- src/mui/form/getDefaultValues.spec.js | 10 +- src/mui/form/validate.js | 34 +- src/mui/form/validate.spec.js | 29 +- src/mui/input/AutocompleteInput.js | 41 +- src/mui/input/AutocompleteInput.spec.js | 172 +- src/mui/input/BooleanInput.js | 22 +- src/mui/input/BooleanInput.spec.js | 12 +- src/mui/input/CheckboxGroupInput.js | 53 +- src/mui/input/CheckboxGroupInput.spec.js | 160 +- src/mui/input/DateInput.spec.js | 38 +- src/mui/input/DisabledInput.js | 23 +- src/mui/input/FileInput.spec.js | 72 +- src/mui/input/FileInputPreview.js | 15 +- src/mui/input/Labeled.js | 28 +- src/mui/input/LongTextInput.js | 28 +- src/mui/input/LongTextInput.spec.js | 18 +- src/mui/input/NullableBooleanInput.js | 13 +- src/mui/input/NullableBooleanInput.spec.js | 33 +- src/mui/input/NumberInput.js | 37 +- src/mui/input/NumberInput.spec.js | 22 +- src/mui/input/RadioButtonGroupInput.js | 45 +- src/mui/input/RadioButtonGroupInput.spec.js | 160 +- src/mui/input/ReferenceArrayInput.js | 69 +- src/mui/input/ReferenceArrayInput.spec.js | 64 +- src/mui/input/ReferenceInput.js | 72 +- src/mui/input/ReferenceInput.spec.js | 113 +- src/mui/input/SelectArrayInput.js | 72 +- src/mui/input/SelectArrayInput.spec.js | 184 +- src/mui/input/SelectInput.js | 44 +- src/mui/input/SelectInput.spec.js | 197 +- src/mui/input/TextInput.js | 21 +- src/mui/input/TextInput.spec.js | 32 +- src/mui/layout/AppBar.js | 16 +- src/mui/layout/AppBarMobile.js | 12 +- src/mui/layout/DashboardMenuItem.js | 5 +- src/mui/layout/Layout.js | 36 +- src/mui/layout/Menu.js | 25 +- src/mui/layout/MenuItemLink.js | 19 +- src/mui/layout/Notification.js | 36 +- src/mui/layout/Responsive.js | 22 +- src/mui/layout/Responsive.spec.js | 93 +- src/mui/layout/Sidebar.js | 28 +- src/mui/layout/Title.js | 19 +- src/mui/layout/ViewTitle.js | 5 +- src/mui/list/Actions.js | 29 +- src/mui/list/Datagrid.js | 68 +- src/mui/list/DatagridBody.js | 48 +- src/mui/list/DatagridCell.js | 19 +- src/mui/list/DatagridCell.spec.js | 17 +- src/mui/list/DatagridHeaderCell.js | 73 +- src/mui/list/DatagridHeaderCell.spec.js | 12 +- src/mui/list/Filter.js | 32 +- src/mui/list/Filter.spec.js | 4 +- src/mui/list/FilterButton.js | 65 +- src/mui/list/FilterButtonMenuItem.js | 12 +- src/mui/list/FilterForm.js | 99 +- src/mui/list/FilterForm.spec.js | 2 +- src/mui/list/List.js | 214 +- src/mui/list/List.spec.js | 2 +- src/mui/list/Pagination.js | 127 +- src/mui/list/Pagination.spec.js | 44 +- src/mui/list/SimpleList.js | 12 +- src/mui/list/SingleFieldList.js | 5 +- src/reducer/index.js | 9 +- src/reducer/loading.js | 35 +- src/reducer/loading.spec.js | 13 +- src/reducer/locale.js | 9 +- src/reducer/locale.spec.js | 5 +- src/reducer/notification.js | 17 +- src/reducer/notification.spec.js | 27 +- src/reducer/references/oneToMany.js | 20 +- src/reducer/references/possibleValues.js | 32 +- src/reducer/resource/data.js | 40 +- src/reducer/resource/index.js | 9 +- src/reducer/resource/list/ids.js | 37 +- src/reducer/resource/list/index.js | 11 +- src/reducer/resource/list/params.js | 13 +- src/reducer/resource/list/queryReducer.js | 39 +- .../resource/list/queryReducer.spec.js | 65 +- src/reducer/saving.js | 32 +- src/reducer/ui.js | 15 +- src/reducer/ui.spec.js | 30 +- src/rest/jsonServer.js | 141 +- src/rest/simple.js | 147 +- src/sideEffect/saga/auth.js | 95 +- src/sideEffect/saga/crudFetch.js | 40 +- src/sideEffect/saga/crudResponse.js | 113 +- src/sideEffect/saga/crudSaga.js | 17 +- src/sideEffect/saga/referenceFetch.js | 11 +- src/util/FieldTitle.js | 27 +- src/util/FieldTitle.spec.js | 84 +- src/util/HttpError.js | 2 +- src/util/fetch.js | 31 +- src/util/linkToRecord.js | 3 +- src/util/linkToRecord.spec.js | 13 +- src/util/removeKey.js | 5 +- src/util/resolveRedirectTo.js | 20 +- 175 files changed, 12776 insertions(+), 2491 deletions(-) create mode 100644 package-lock.json diff --git a/.eslintrc b/.eslintrc index a56bc64d..8b38207c 100644 --- a/.eslintrc +++ b/.eslintrc @@ -4,16 +4,46 @@ "mocha": true, "node": true, "phantomjs": true, - "protractor": true, + "protractor": true }, - "extends": "airbnb", + "plugins": [ + "prettier", + "react" + ], + "extends": [ + "prettier", + "plugin:react/recommended" + ], "parser": "babel-eslint", "rules": { - "indent": ["warn", 4], - "max-len": ["off"], - "react/forbid-prop-types": ["warn", { "forbid": ["any", "array"] }], - "react/jsx-indent": ["warn", 4], - "react/jsx-indent-props": ["warn", 4], - "react/jsx-filename-extension": ["off"] - }, -} + "prettier/prettier": [ + "error", + { + "singleQuote": true, + "tabWidth": 4, + "trailingComma": "es5" + } + ], + "react/forbid-prop-types": [ + "warn", + { + "forbid": [ + "any", + "array" + ] + } + ], + "react/prop-types": [ + "warn" + ], + "react/jsx-indent": [ + "off" + ], + "react/jsx-indent-props": [ + "off" + ], + "react/jsx-filename-extension": [ + "off" + ] + } +} \ No newline at end of file diff --git a/example/addUploadFeature.js b/example/addUploadFeature.js index b88882c3..a71afe94 100644 --- a/example/addUploadFeature.js +++ b/example/addUploadFeature.js @@ -1,30 +1,42 @@ -const convertFileToBase64 = file => new Promise((resolve, reject) => { - const reader = new FileReader(); - reader.readAsDataURL(file); +const convertFileToBase64 = file => + new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.readAsDataURL(file); - reader.onload = () => resolve(reader.result); - reader.onerror = reject; -}); + reader.onload = () => resolve(reader.result); + reader.onerror = reject; + }); const addUploadCapabilities = requestHandler => (type, resource, params) => { if (type === 'UPDATE' && resource === 'posts') { if (params.data.pictures && params.data.pictures.length) { // only freshly dropped pictures are instance of File - const formerPictures = params.data.pictures.filter(p => !(p instanceof File)); - const newPictures = params.data.pictures.filter(p => p instanceof File); + const formerPictures = params.data.pictures.filter( + p => !(p instanceof File) + ); + const newPictures = params.data.pictures.filter( + p => p instanceof File + ); return Promise.all(newPictures.map(convertFileToBase64)) - .then(base64Pictures => base64Pictures.map(picture64 => ({ - src: picture64, - title: `${params.data.title}`, - }))) - .then(transformedNewPictures => requestHandler(type, resource, { - ...params, - data: { - ...params.data, - pictures: [...transformedNewPictures, ...formerPictures], - }, - })); + .then(base64Pictures => + base64Pictures.map(picture64 => ({ + src: picture64, + title: `${params.data.title}`, + })) + ) + .then(transformedNewPictures => + requestHandler(type, resource, { + ...params, + data: { + ...params.data, + pictures: [ + ...transformedNewPictures, + ...formerPictures, + ], + }, + }) + ); } } diff --git a/example/app.js b/example/app.js index 0a9e32fe..76fe070e 100644 --- a/example/app.js +++ b/example/app.js @@ -9,7 +9,13 @@ import frenchMessages from 'aor-language-french'; import addUploadFeature from './addUploadFeature'; import { PostList, PostCreate, PostEdit, PostShow, PostIcon } from './posts'; -import { CommentList, CommentEdit, CommentCreate, CommentIcon, CommentShow } from './comments'; +import { + CommentList, + CommentEdit, + CommentCreate, + CommentShow, + CommentIcon, +} from './comments'; import data from './data'; import * as customMessages from './i18n'; @@ -22,13 +28,41 @@ const messages = { const restClient = jsonRestClient(data, true); const uploadCapableClient = addUploadFeature(restClient); -const delayedRestClient = (type, resource, params) => new Promise(resolve => setTimeout(() => resolve(uploadCapableClient(type, resource, params)), 1000)); +const delayedRestClient = (type, resource, params) => + new Promise(resolve => + setTimeout( + () => resolve(uploadCapableClient(type, resource, params)), + 1000 + ) + ); render( - - - + + + , - document.getElementById('root'), + document.getElementById('root') ); diff --git a/example/authClient.js b/example/authClient.js index 9a676218..2d8aac99 100644 --- a/example/authClient.js +++ b/example/authClient.js @@ -17,10 +17,14 @@ export default (type, params) => { } if (type === AUTH_ERROR) { const { status } = params; - return (status === 401 || status === 403) ? Promise.reject() : Promise.resolve(); + return status === 401 || status === 403 + ? Promise.reject() + : Promise.resolve(); } if (type === AUTH_CHECK) { - return localStorage.getItem('not_authenticated') ? Promise.reject() : Promise.resolve(); + return localStorage.getItem('not_authenticated') + ? Promise.reject() + : Promise.resolve(); } return Promise.reject('Unknown method'); }; diff --git a/example/comments.js b/example/comments.js index 88f7d22c..9ab5ab9e 100644 --- a/example/comments.js +++ b/example/comments.js @@ -41,22 +41,38 @@ const CommentFilter = ({ ...props }) => ( ); -const CommentPagination = translate(({ page, perPage, total, setPage, translate }) => { - const nbPages = Math.ceil(total / perPage) || 1; - return ( - nbPages > 1 && - - - {page > 1 && - } onClick={() => setPage(page - 1)} /> - } - {page !== nbPages && - } onClick={() => setPage(page + 1)} labelPosition="before" /> - } - - - ); -}); +const CommentPagination = translate( + ({ page, perPage, total, setPage, translate }) => { + const nbPages = Math.ceil(total / perPage) || 1; + return ( + nbPages > 1 && ( + + + {page > 1 && ( + } + onClick={() => setPage(page - 1)} + /> + )} + {page !== nbPages && ( + } + onClick={() => setPage(page + 1)} + labelPosition="before" + /> + )} + + + ) + ); + } +); const cardStyle = { width: 300, @@ -68,28 +84,44 @@ const cardStyle = { const CommentGrid = translate(({ ids, data, basePath, translate }) => (
- {ids.map(id => - - } - subtitle={} - avatar={} />} - /> - - - - - {translate('comment.list.about')}  - - - - - - - - - , - )} + {ids.map(id => ( + + } + subtitle={ + + } + avatar={} />} + /> + + + + + {translate('comment.list.about')}  + + + + + + + + + + ))}
)); @@ -98,19 +130,25 @@ CommentGrid.defaultProps = { ids: [], }; -const CommentMobileList = (props) => ( +const CommentMobileList = props => ( record.author.name} secondaryText={record => record.body} secondaryTextLines={2} - tertiaryText={record => new Date(record.created_at).toLocaleDateString()} + tertiaryText={record => + new Date(record.created_at).toLocaleDateString()} leftAvatar={() => } />} {...props} /> ); export const CommentList = ({ ...props }) => ( - } pagination={}> + } + pagination={} + > } medium={} /> ); @@ -119,7 +157,12 @@ export const CommentEdit = ({ ...props }) => ( - + @@ -131,8 +174,13 @@ export const CommentEdit = ({ ...props }) => ( export const CommentCreate = ({ ...props }) => ( - - + + @@ -141,12 +189,12 @@ export const CommentCreate = ({ ...props }) => ( ); -export const CommentShow = ({...props}) => ( +export const CommentShow = ({ ...props }) => ( - + diff --git a/example/data.js b/example/data.js index 8787f37d..bf9d5e1a 100644 --- a/example/data.js +++ b/example/data.js @@ -2,9 +2,12 @@ export default { posts: [ { id: 1, - title: 'Accusantium qui nihil voluptatum quia voluptas maxime ab similique', - teaser: 'In facilis aut aut odit hic doloribus. Fugit possimus perspiciatis sit molestias in. Sunt dignissimos sed quis at vitae veniam amet. Sint sunt perspiciatis quis doloribus aperiam numquam consequatur et. Blanditiis aut earum incidunt eos magnam et voluptatem. Minima iure voluptatum autem. At eaque sit aperiam minima aut in illum.', - body: '

Rerum velit quos est similique. Consectetur tempora eos ullam velit nobis sit debitis. Magni explicabo omnis delectus labore vel recusandae.

Aut a minus laboriosam harum placeat quas minima fuga. Quos nulla fuga quam officia tempore. Rerum occaecati ut eum et tempore. Nam ab repudiandae et nemo praesentium.

Cumque corporis officia occaecati ducimus sequi laborum omnis ut. Nam aspernatur veniam fugit. Nihil eum libero ea dolorum ducimus impedit sed. Quidem inventore porro corporis debitis eum in. Nesciunt unde est est qui nulla. Esse sunt placeat molestiae molestiae sed quia. Sunt qui quidem quos velit reprehenderit quos blanditiis ducimus. Sint et molestiae maxime ut consequatur minima. Quaerat rem voluptates voluptatem quos. Corporis perferendis in provident iure. Commodi odit exercitationem excepturi et deserunt qui.

Optio iste necessitatibus velit non. Neque sed occaecati culpa porro culpa. Quia quam in molestias ratione et necessitatibus consequatur. Est est tempora consequatur voluptatem vel. Mollitia tenetur non quis omnis perspiciatis deserunt sed necessitatibus. Ad rerum reiciendis sunt aspernatur.

Est ullam ut magni aspernatur. Eum et sed tempore modi.

Earum aperiam sit neque quo laborum suscipit unde. Expedita nostrum itaque non non adipisci. Ut delectus quis delectus est at sint. Iste hic qui ea eaque eaque sed id. Hic placeat rerum numquam id velit deleniti voluptatem. Illum adipisci voluptas adipisci ut alias. Earum exercitationem iste quidem eveniet aliquid hic reiciendis. Exercitationem est sunt in minima consequuntur. Aut quaerat libero dolorem.

', + title: + 'Accusantium qui nihil voluptatum quia voluptas maxime ab similique', + teaser: + 'In facilis aut aut odit hic doloribus. Fugit possimus perspiciatis sit molestias in. Sunt dignissimos sed quis at vitae veniam amet. Sint sunt perspiciatis quis doloribus aperiam numquam consequatur et. Blanditiis aut earum incidunt eos magnam et voluptatem. Minima iure voluptatum autem. At eaque sit aperiam minima aut in illum.', + body: + '

Rerum velit quos est similique. Consectetur tempora eos ullam velit nobis sit debitis. Magni explicabo omnis delectus labore vel recusandae.

Aut a minus laboriosam harum placeat quas minima fuga. Quos nulla fuga quam officia tempore. Rerum occaecati ut eum et tempore. Nam ab repudiandae et nemo praesentium.

Cumque corporis officia occaecati ducimus sequi laborum omnis ut. Nam aspernatur veniam fugit. Nihil eum libero ea dolorum ducimus impedit sed. Quidem inventore porro corporis debitis eum in. Nesciunt unde est est qui nulla. Esse sunt placeat molestiae molestiae sed quia. Sunt qui quidem quos velit reprehenderit quos blanditiis ducimus. Sint et molestiae maxime ut consequatur minima. Quaerat rem voluptates voluptatem quos. Corporis perferendis in provident iure. Commodi odit exercitationem excepturi et deserunt qui.

Optio iste necessitatibus velit non. Neque sed occaecati culpa porro culpa. Quia quam in molestias ratione et necessitatibus consequatur. Est est tempora consequatur voluptatem vel. Mollitia tenetur non quis omnis perspiciatis deserunt sed necessitatibus. Ad rerum reiciendis sunt aspernatur.

Est ullam ut magni aspernatur. Eum et sed tempore modi.

Earum aperiam sit neque quo laborum suscipit unde. Expedita nostrum itaque non non adipisci. Ut delectus quis delectus est at sint. Iste hic qui ea eaque eaque sed id. Hic placeat rerum numquam id velit deleniti voluptatem. Illum adipisci voluptas adipisci ut alias. Earum exercitationem iste quidem eveniet aliquid hic reiciendis. Exercitationem est sunt in minima consequuntur. Aut quaerat libero dolorem.

', views: 143, average_note: 2.72198, commentable: true, @@ -47,8 +50,10 @@ export default { { id: 2, title: 'Sint dignissimos in architecto aut', - teaser: 'Quam earum itaque corrupti labore quas nihil sed. Dolores sunt culpa voluptates exercitationem eveniet totam rerum. Molestias perspiciatis rem numquam accusamus.', - body: '

Aliquam magni tempora quas enim. Perspiciatis libero corporis sunt eum nam. Molestias est sunt molestiae natus.

Blanditiis dignissimos autem culpa itaque. Explicabo perferendis ullam officia ut quia nemo. Eaque perspiciatis perspiciatis est hic non ullam et. Expedita exercitationem enim sit ut dolore.

Sed in sunt officia blanditiis ipsam maiores perspiciatis amet

Vero fugiat facere officiis aut quis rerum velit. Autem eius sint ullam. Nemo sunt molestiae nulla accusantium est voluptatem voluptas sed. In blanditiis neque libero voluptatem praesentium occaecati nulla libero. Perspiciatis eos voluptatem facere voluptatibus. Explicabo quo eveniet nihil culpa. Qui eos officia consequuntur sed esse praesentium dolorum. Eius perferendis qui quia autem nostrum sed. Illum in ex excepturi voluptas. Qui veniam sit alias delectus nihil. Impedit est ut alias illum repellendus qui.

Veniam est aperiam quisquam soluta. Magni blanditiis praesentium sed similique velit ipsam consequatur. Porro omnis magni sunt incidunt aspernatur ut.

', + teaser: + 'Quam earum itaque corrupti labore quas nihil sed. Dolores sunt culpa voluptates exercitationem eveniet totam rerum. Molestias perspiciatis rem numquam accusamus.', + body: + '

Aliquam magni tempora quas enim. Perspiciatis libero corporis sunt eum nam. Molestias est sunt molestiae natus.

Blanditiis dignissimos autem culpa itaque. Explicabo perferendis ullam officia ut quia nemo. Eaque perspiciatis perspiciatis est hic non ullam et. Expedita exercitationem enim sit ut dolore.

Sed in sunt officia blanditiis ipsam maiores perspiciatis amet

Vero fugiat facere officiis aut quis rerum velit. Autem eius sint ullam. Nemo sunt molestiae nulla accusantium est voluptatem voluptas sed. In blanditiis neque libero voluptatem praesentium occaecati nulla libero. Perspiciatis eos voluptatem facere voluptatibus. Explicabo quo eveniet nihil culpa. Qui eos officia consequuntur sed esse praesentium dolorum. Eius perferendis qui quia autem nostrum sed. Illum in ex excepturi voluptas. Qui veniam sit alias delectus nihil. Impedit est ut alias illum repellendus qui.

Veniam est aperiam quisquam soluta. Magni blanditiis praesentium sed similique velit ipsam consequatur. Porro omnis magni sunt incidunt aspernatur ut.

', views: 563, average_note: 3.48121, commentable: true, @@ -61,8 +66,10 @@ export default { { id: 3, title: 'Perspiciatis adipisci vero qui ipsam iure porro', - teaser: 'Ut ad consequatur esse illum. Ex dolore porro et ut sit. Commodi qui sed et voluptatibus laudantium.', - body: '

Voluptatibus fugit sit praesentium voluptas vero vel. Reprehenderit quam cupiditate deleniti ipsum nisi qui. Molestiae modi sequi vel quibusdam est aliquid doloribus. Necessitatibus et excepturi alias necessitatibus magnam ea.

Dolor illum dolores qui et pariatur inventore incidunt molestias. Exercitationem ipsum voluptatibus voluptatum velit sint vel qui. Odit mollitia minus vitae impedit voluptatem. Voluptas ullam temporibus inventore fugiat pariatur odit molestias.

Atque est qui alias eum. Quibusdam rem ut dolores voluptate totam. Sit cumque perferendis sed a iusto laudantium quae et. Voluptatibus vitae natus quia laboriosam et deserunt. Doloribus fuga aut quo tempora animi eaque consequatur laboriosam.

', + teaser: + 'Ut ad consequatur esse illum. Ex dolore porro et ut sit. Commodi qui sed et voluptatibus laudantium.', + body: + '

Voluptatibus fugit sit praesentium voluptas vero vel. Reprehenderit quam cupiditate deleniti ipsum nisi qui. Molestiae modi sequi vel quibusdam est aliquid doloribus. Necessitatibus et excepturi alias necessitatibus magnam ea.

Dolor illum dolores qui et pariatur inventore incidunt molestias. Exercitationem ipsum voluptatibus voluptatum velit sint vel qui. Odit mollitia minus vitae impedit voluptatem. Voluptas ullam temporibus inventore fugiat pariatur odit molestias.

Atque est qui alias eum. Quibusdam rem ut dolores voluptate totam. Sit cumque perferendis sed a iusto laudantium quae et. Voluptatibus vitae natus quia laboriosam et deserunt. Doloribus fuga aut quo tempora animi eaque consequatur laboriosam.

', views: 467, commentable: true, published_at: new Date('2012-08-08'), @@ -91,8 +98,10 @@ export default { { id: 4, title: 'Maiores et itaque aut perspiciatis', - teaser: 'Et quo voluptas odit veniam omnis dolores. Odit commodi consequuntur necessitatibus dolorem officia. Reiciendis quas exercitationem libero sed. Itaque non facilis sit tempore aut doloribus.', - body: '

Sunt sunt aut est et consequatur ea dolores. Voluptatem rerum cupiditate dolore. Voluptas sit sapiente corrupti error ducimus. Qui enim aut possimus qui. Impedit voluptatem sed inventore iusto et ut et. Maxime sunt qui adipisci expedita quisquam. Velit ea ut in blanditiis eos doloribus.

Qui optio ad magnam eius. Est id velit ratione eum corrupti non vitae. Quam consequatur animi sed corrupti quae sed deserunt. Accusamus eius eos recusandae eum quia id.

Voluptas omnis omnis culpa est vel eum. Ut in tempore harum voluptates odit delectus sit et. Consequuntur quod nihil veniam natus placeat provident. Totam ut fuga vitae in. Possimus cumque quae voluptatem asperiores vitae officiis dolores. Qui autem eos dolores eius. Iure ut delectus quis voluptatem. Velit at incidunt minus laboriosam culpa. Pariatur ipsa ut enim dolor. Sed magni sunt molestiae voluptas ut illum. Sit consequuntur laborum aliquid delectus in. Consectetur dicta asperiores itaque aut mollitia. Minus praesentium officiis voluptas a officiis ad beatae.

', + teaser: + 'Et quo voluptas odit veniam omnis dolores. Odit commodi consequuntur necessitatibus dolorem officia. Reiciendis quas exercitationem libero sed. Itaque non facilis sit tempore aut doloribus.', + body: + '

Sunt sunt aut est et consequatur ea dolores. Voluptatem rerum cupiditate dolore. Voluptas sit sapiente corrupti error ducimus. Qui enim aut possimus qui. Impedit voluptatem sed inventore iusto et ut et. Maxime sunt qui adipisci expedita quisquam. Velit ea ut in blanditiis eos doloribus.

Qui optio ad magnam eius. Est id velit ratione eum corrupti non vitae. Quam consequatur animi sed corrupti quae sed deserunt. Accusamus eius eos recusandae eum quia id.

Voluptas omnis omnis culpa est vel eum. Ut in tempore harum voluptates odit delectus sit et. Consequuntur quod nihil veniam natus placeat provident. Totam ut fuga vitae in. Possimus cumque quae voluptatem asperiores vitae officiis dolores. Qui autem eos dolores eius. Iure ut delectus quis voluptatem. Velit at incidunt minus laboriosam culpa. Pariatur ipsa ut enim dolor. Sed magni sunt molestiae voluptas ut illum. Sit consequuntur laborum aliquid delectus in. Consectetur dicta asperiores itaque aut mollitia. Minus praesentium officiis voluptas a officiis ad beatae.

', views: 685, average_note: 1.2319, commentable: false, @@ -104,8 +113,10 @@ export default { { id: 5, title: 'Sed quo et et fugiat modi', - teaser: 'Consequuntur id aut soluta aspernatur sit. Aut doloremque recusandae sit saepe ut quas earum. Quae pariatur iure et ducimus non. Cupiditate dolorem itaque in sit.', - body: '

Aut molestiae quae explicabo voluptas. Assumenda ea ipsam quia. Rerum rerum magnam sunt doloremque dolorem nulla. Eveniet ut aliquam est dignissimos nisi molestias dicta. Dolorum et id esse illum. Ea omnis nesciunt tempore et aut. Ut ullam totam doloribus recusandae est natus voluptatum officiis. Ea quam eos velit ipsam non accusamus praesentium.

Animi et minima alias sint. Reiciendis qui ipsam autem fugit consequuntur veniam. Vel cupiditate voluptas enim dolore cum ad. Ut iusto eius et.

Quis praesentium aut aut aut voluptas et. Quam laudantium at laudantium amet. Earum quidem eos earum quaerat nihil libero quia sed.

Autem voluptatem nostrum ullam numquam quis. Et aut unde nesciunt officiis nam eos ut distinctio. Animi est explicabo voluptas officia quos necessitatibus. Omnis debitis unde et qui rerum. Nisi repudiandae autem mollitia dolorum veritatis aut. Rem temporibus labore repellendus enim consequuntur dicta autem. Illum illo inventore possimus officiis quidem.

Ullam accusantium eaque perspiciatis. Quidem dolor minus aut quidem. Praesentium earum beatae eos eligendi nostrum. Dolor nam quo aut.

Accusamus aut tempora omnis magni sit quos eos aut. Vitae ut inventore facere neque rerum. Qui esse rem cupiditate sit.

Est minus odio sint reprehenderit. Consectetur dolores eligendi et quaerat sint vel magni. Voluptatum hic cum placeat ad ea reiciendis laborum et. Eos ab id suscipit.

', + teaser: + 'Consequuntur id aut soluta aspernatur sit. Aut doloremque recusandae sit saepe ut quas earum. Quae pariatur iure et ducimus non. Cupiditate dolorem itaque in sit.', + body: + '

Aut molestiae quae explicabo voluptas. Assumenda ea ipsam quia. Rerum rerum magnam sunt doloremque dolorem nulla. Eveniet ut aliquam est dignissimos nisi molestias dicta. Dolorum et id esse illum. Ea omnis nesciunt tempore et aut. Ut ullam totam doloribus recusandae est natus voluptatum officiis. Ea quam eos velit ipsam non accusamus praesentium.

Animi et minima alias sint. Reiciendis qui ipsam autem fugit consequuntur veniam. Vel cupiditate voluptas enim dolore cum ad. Ut iusto eius et.

Quis praesentium aut aut aut voluptas et. Quam laudantium at laudantium amet. Earum quidem eos earum quaerat nihil libero quia sed.

Autem voluptatem nostrum ullam numquam quis. Et aut unde nesciunt officiis nam eos ut distinctio. Animi est explicabo voluptas officia quos necessitatibus. Omnis debitis unde et qui rerum. Nisi repudiandae autem mollitia dolorum veritatis aut. Rem temporibus labore repellendus enim consequuntur dicta autem. Illum illo inventore possimus officiis quidem.

Ullam accusantium eaque perspiciatis. Quidem dolor minus aut quidem. Praesentium earum beatae eos eligendi nostrum. Dolor nam quo aut.

Accusamus aut tempora omnis magni sit quos eos aut. Vitae ut inventore facere neque rerum. Qui esse rem cupiditate sit.

Est minus odio sint reprehenderit. Consectetur dolores eligendi et quaerat sint vel magni. Voluptatum hic cum placeat ad ea reiciendis laborum et. Eos ab id suscipit.

', views: 559, average_note: 3, commentable: true, @@ -116,8 +127,10 @@ export default { { id: 6, title: 'Minima ea vero omnis odit officiis aut', - teaser: 'Omnis rerum voluptatem illum. Amet totam minus id qui aspernatur. Adipisci commodi velit sapiente architecto et molestias. Maiores doloribus quis occaecati quidem laborum. Quae quia quaerat est itaque. Vero assumenda quia tempora libero dicta quis asperiores magnam. Necessitatibus accusantium saepe commodi ut.', - body: '

Sit autem rerum inventore repellendus. Enim placeat est ea dolor voluptas nisi alias. Repellat quam laboriosam repudiandae illum similique omnis non exercitationem. Modi mollitia omnis sed vel et expedita fugiat. Esse laboriosam doloribus deleniti atque quidem praesentium aliquid. Error animi ab excepturi quia. Et voluptates voluptatem et est quibusdam aspernatur. Fugiat consequatur veritatis commodi enim quaerat sint. Quis quae fuga exercitationem dolorem enim laborum numquam. Iste necessitatibus repellat in ea nihil et rem. Corporis dolores sed vitae consectetur dolores qui dicta. Laudantium et suscipit odit quidem qui. Provident libero eveniet distinctio debitis odio cum id dolorum. Consequuntur laboriosam qui ut magni sit dicta. Distinctio fugit voluptatibus voluptatem suscipit incidunt ut cupiditate. Magni harum in aut alias veniam. Eos aut impedit ut et. Iure aliquid adipisci aliquam et ab et qui. Itaque quod consequuntur dolore asperiores architecto neque. Exercitationem eum voluptas ut quis hic quo. Omnis quas porro laudantium. Qui magnam et totam quibusdam in quo. Impedit laboriosam eum sint soluta facere ut voluptatem.

', + teaser: + 'Omnis rerum voluptatem illum. Amet totam minus id qui aspernatur. Adipisci commodi velit sapiente architecto et molestias. Maiores doloribus quis occaecati quidem laborum. Quae quia quaerat est itaque. Vero assumenda quia tempora libero dicta quis asperiores magnam. Necessitatibus accusantium saepe commodi ut.', + body: + '

Sit autem rerum inventore repellendus. Enim placeat est ea dolor voluptas nisi alias. Repellat quam laboriosam repudiandae illum similique omnis non exercitationem. Modi mollitia omnis sed vel et expedita fugiat. Esse laboriosam doloribus deleniti atque quidem praesentium aliquid. Error animi ab excepturi quia. Et voluptates voluptatem et est quibusdam aspernatur. Fugiat consequatur veritatis commodi enim quaerat sint. Quis quae fuga exercitationem dolorem enim laborum numquam. Iste necessitatibus repellat in ea nihil et rem. Corporis dolores sed vitae consectetur dolores qui dicta. Laudantium et suscipit odit quidem qui. Provident libero eveniet distinctio debitis odio cum id dolorum. Consequuntur laboriosam qui ut magni sit dicta. Distinctio fugit voluptatibus voluptatem suscipit incidunt ut cupiditate. Magni harum in aut alias veniam. Eos aut impedit ut et. Iure aliquid adipisci aliquam et ab et qui. Itaque quod consequuntur dolore asperiores architecto neque. Exercitationem eum voluptas ut quis hic quo. Omnis quas porro laudantium. Qui magnam et totam quibusdam in quo. Impedit laboriosam eum sint soluta facere ut voluptatem.

', views: 208, average_note: 3.1214, published_at: new Date('2012-09-05'), @@ -128,8 +141,10 @@ export default { { id: 7, title: 'Illum veritatis corrupti exercitationem sed velit', - teaser: 'Omnis hic quo aperiam fugiat iure amet est. Molestias ratione aut et dolor earum magnam placeat. Ad a quam ea amet hic omnis rerum.', - body: '

Omnis sunt maxime qui consequatur perspiciatis et dolor. Assumenda numquam sit rerum aut dolores. Repudiandae rerum et quisquam. Perferendis cupiditate sequi non similique eum accusamus voluptas.

Officiis in voluptatum culpa ut eaque laborum. Sit quos velit sed ad voluptates. Alias aut quo accusantium aut cumque perferendis. Numquam rerum vel et est delectus. Mollitia dolores voluptatum accusantium id rem. Autem dolorem similique earum. Deleniti qui iusto et vero. Enim quaerat ipsum omnis magni. Autem magnam vero nulla impedit distinctio. Sequi laudantium ut animi enim recusandae et voluptatum. Dicta architecto nostrum voluptas consequuntur ea. Porro odio illo praesentium qui. Quia sit sed labore porro. Minima odit nemo sint praesentium. Ea sapiente quis aut. Qui cumque aut repudiandae in. Ipsam mollitia ab vitae iusto maxime. Eaque qui impedit et ea dolor aut. Tenetur ut nihil sed. Eum doloremque harum ipsam vel eos ut enim.

', + teaser: + 'Omnis hic quo aperiam fugiat iure amet est. Molestias ratione aut et dolor earum magnam placeat. Ad a quam ea amet hic omnis rerum.', + body: + '

Omnis sunt maxime qui consequatur perspiciatis et dolor. Assumenda numquam sit rerum aut dolores. Repudiandae rerum et quisquam. Perferendis cupiditate sequi non similique eum accusamus voluptas.

Officiis in voluptatum culpa ut eaque laborum. Sit quos velit sed ad voluptates. Alias aut quo accusantium aut cumque perferendis. Numquam rerum vel et est delectus. Mollitia dolores voluptatum accusantium id rem. Autem dolorem similique earum. Deleniti qui iusto et vero. Enim quaerat ipsum omnis magni. Autem magnam vero nulla impedit distinctio. Sequi laudantium ut animi enim recusandae et voluptatum. Dicta architecto nostrum voluptas consequuntur ea. Porro odio illo praesentium qui. Quia sit sed labore porro. Minima odit nemo sint praesentium. Ea sapiente quis aut. Qui cumque aut repudiandae in. Ipsam mollitia ab vitae iusto maxime. Eaque qui impedit et ea dolor aut. Tenetur ut nihil sed. Eum doloremque harum ipsam vel eos ut enim.

', views: 133, average_note: null, commentable: true, @@ -140,9 +155,12 @@ export default { }, { id: 8, - title: 'Culpa possimus quibusdam nostrum enim tempore rerum odit excepturi', - teaser: 'Qui quos exercitationem itaque quia. Repellat libero ut recusandae quidem repudiandae ipsam laudantium. Eveniet quos et quo omnis aut commodi incidunt.', - body: '

Laudantium voluptatem non facere officiis qui natus natus. Ex perspiciatis quia dolor earum. In rerum deleniti voluptas quo quia adipisci voluptatibus.

Mollitia eos quaerat ad. Et non aliquam velit. Doloremque repudiandae earum suscipit deleniti.

Debitis voluptatem possimus saepe. Rerum nam est neque voluptate quae ratione et quaerat. Fugiat et ullam adipisci numquam. Atque qui cum quae quod qui reprehenderit. Veritatis odio eligendi est odit minima ut dolores. Blanditiis aut rem aliquam nulla esse odit. Quibusdam quam natus eos tenetur nemo eligendi velit nam. Consequatur libero eius quia impedit neque fuga. Accusantium sunt accusantium eaque illum dicta. Expedita explicabo quia soluta.

Dolores aperiam rem velit id provident quo ea. Modi illum voluptate corrupti recusandae optio. Voluptatem architecto numquam reiciendis quo nostrum suscipit. Dolore repellat deleniti nihil omnis illum explicabo nihil. Alias maxime hic minus voluptas odio id dolorum. Neque perferendis repellendus autem consequatur consequatur doloribus. Sit aspernatur nisi aliquam rem voluptas occaecati.

In eveniet nostrum culpa totam officia doloremque. Fugiat maxime magni aut magnam praesentium vel facere. Tempora soluta possimus omnis modi et qui minus. Consequatur et suscipit autem quia nulla.

Qui eum aliquid inventore at. Qui provident perspiciatis sed eum eos sunt eveniet autem. Ducimus velit tenetur sed. Quas laboriosam dicta ipsa id fugiat. Hic nihil laboriosam atque natus. Quam natus esse est error molestiae nulla. Odit ut dolorem laborum quidem quis alias. Labore sint porro et reprehenderit ut dolorem vel dolorum. Dolores suscipit ut dolores possimus id dicta cupiditate. Est cum dolorum dolores ducimus quia reprehenderit. Iste suscipit molestias voluptatem molestiae. Nostrum modi dicta qui deleniti. Reprehenderit voluptatem soluta non in labore. Voluptatem ut illo illo harum voluptas cumque. Tempora illo distinctio qui aut.

Eaque voluptatem eos omnis qui dolor non possimus. Distinctio ratione facere doloremque rerum qui voluptas et. Cum incidunt numquam molestias et labore odio sunt aut. Aut pariatur dignissimos est atque.

', + title: + 'Culpa possimus quibusdam nostrum enim tempore rerum odit excepturi', + teaser: + 'Qui quos exercitationem itaque quia. Repellat libero ut recusandae quidem repudiandae ipsam laudantium. Eveniet quos et quo omnis aut commodi incidunt.', + body: + '

Laudantium voluptatem non facere officiis qui natus natus. Ex perspiciatis quia dolor earum. In rerum deleniti voluptas quo quia adipisci voluptatibus.

Mollitia eos quaerat ad. Et non aliquam velit. Doloremque repudiandae earum suscipit deleniti.

Debitis voluptatem possimus saepe. Rerum nam est neque voluptate quae ratione et quaerat. Fugiat et ullam adipisci numquam. Atque qui cum quae quod qui reprehenderit. Veritatis odio eligendi est odit minima ut dolores. Blanditiis aut rem aliquam nulla esse odit. Quibusdam quam natus eos tenetur nemo eligendi velit nam. Consequatur libero eius quia impedit neque fuga. Accusantium sunt accusantium eaque illum dicta. Expedita explicabo quia soluta.

Dolores aperiam rem velit id provident quo ea. Modi illum voluptate corrupti recusandae optio. Voluptatem architecto numquam reiciendis quo nostrum suscipit. Dolore repellat deleniti nihil omnis illum explicabo nihil. Alias maxime hic minus voluptas odio id dolorum. Neque perferendis repellendus autem consequatur consequatur doloribus. Sit aspernatur nisi aliquam rem voluptas occaecati.

In eveniet nostrum culpa totam officia doloremque. Fugiat maxime magni aut magnam praesentium vel facere. Tempora soluta possimus omnis modi et qui minus. Consequatur et suscipit autem quia nulla.

Qui eum aliquid inventore at. Qui provident perspiciatis sed eum eos sunt eveniet autem. Ducimus velit tenetur sed. Quas laboriosam dicta ipsa id fugiat. Hic nihil laboriosam atque natus. Quam natus esse est error molestiae nulla. Odit ut dolorem laborum quidem quis alias. Labore sint porro et reprehenderit ut dolorem vel dolorum. Dolores suscipit ut dolores possimus id dicta cupiditate. Est cum dolorum dolores ducimus quia reprehenderit. Iste suscipit molestias voluptatem molestiae. Nostrum modi dicta qui deleniti. Reprehenderit voluptatem soluta non in labore. Voluptatem ut illo illo harum voluptas cumque. Tempora illo distinctio qui aut.

Eaque voluptatem eos omnis qui dolor non possimus. Distinctio ratione facere doloremque rerum qui voluptas et. Cum incidunt numquam molestias et labore odio sunt aut. Aut pariatur dignissimos est atque.

', views: 557, average_note: null, commentable: false, @@ -154,8 +172,10 @@ export default { { id: 9, title: 'A voluptas eius eveniet ut commodi dolor', - teaser: 'Sed necessitatibus nesciunt nesciunt aut non sunt. Quam ut in a sed ducimus eos qui sint. Commodi illo necessitatibus sint explicabo maiores. Maxime voluptates sit distinctio quo excepturi. Qui aliquid debitis repellendus distinctio et aut. Ex debitis et quasi id.', - body: '

Consequatur temporibus explicabo vel laudantium totam. Voluptates nihil numquam accusamus ut unde quo. Molestiae dolores quas sit aliquam. Sit et fuga necessitatibus natus fugit voluptas et. Esse vitae sed sit eius.

Accusantium aliquam accusamus illo eum. Excepturi molestiae et earum qui. Iste dolor eligendi est vero iure eos nesciunt. Qui aspernatur repellendus id rerum consequatur ut. Quis ab quos fugit dicta aut voluptas. Rerum aut esse dolor. Illo iste ullam possimus nam nam assumenda molestiae est.

In porro nesciunt cumque in sint vel architecto. Aliquam et in numquam quae explicabo. Deserunt suscipit sunt excepturi optio molestiae. Facilis saepe eaque commodi provident ad voluptates eligendi.

Magnam et neque ad sed qui laborum et. Aut dolorem maxime harum. Molestias aut facere vitae voluptatem.

Excepturi odit doloremque eos quisquam sunt. Veniam repudiandae nisi dolorum nam quos. Qui voluptatem enim enim. Dolorum eveniet eaque expedita est tempore. Expedita amet blanditiis esse qui. Nam dolor odio nihil nobis quas quia exercitationem. Iusto ut ut reiciendis sint laudantium et distinctio. Vitae architecto accusamus quos dolores laudantium doloribus alias. Est est esse autem repellat. Assumenda officia aperiam sequi facere distinctio ut. Magnam qui assumenda eligendi sint. Architecto autem harum qui ea quos ut nesciunt et. Optio quidem sit ex quos provident. Et dolor dicta et laudantium. Incidunt id quo enim atque molestiae quam repudiandae omnis. Sed nam voluptatem dolores natus quisquam. Sit nostrum voluptate sed asperiores. Saepe eaque et illum aperiam. Maxime tenetur sunt reiciendis.

Ducimus quia dolorem voluptas ea. Fuga eum architecto eius cum est quibusdam eligendi est. In ut aperiam ea ut.

', + teaser: + 'Sed necessitatibus nesciunt nesciunt aut non sunt. Quam ut in a sed ducimus eos qui sint. Commodi illo necessitatibus sint explicabo maiores. Maxime voluptates sit distinctio quo excepturi. Qui aliquid debitis repellendus distinctio et aut. Ex debitis et quasi id.', + body: + '

Consequatur temporibus explicabo vel laudantium totam. Voluptates nihil numquam accusamus ut unde quo. Molestiae dolores quas sit aliquam. Sit et fuga necessitatibus natus fugit voluptas et. Esse vitae sed sit eius.

Accusantium aliquam accusamus illo eum. Excepturi molestiae et earum qui. Iste dolor eligendi est vero iure eos nesciunt. Qui aspernatur repellendus id rerum consequatur ut. Quis ab quos fugit dicta aut voluptas. Rerum aut esse dolor. Illo iste ullam possimus nam nam assumenda molestiae est.

In porro nesciunt cumque in sint vel architecto. Aliquam et in numquam quae explicabo. Deserunt suscipit sunt excepturi optio molestiae. Facilis saepe eaque commodi provident ad voluptates eligendi.

Magnam et neque ad sed qui laborum et. Aut dolorem maxime harum. Molestias aut facere vitae voluptatem.

Excepturi odit doloremque eos quisquam sunt. Veniam repudiandae nisi dolorum nam quos. Qui voluptatem enim enim. Dolorum eveniet eaque expedita est tempore. Expedita amet blanditiis esse qui. Nam dolor odio nihil nobis quas quia exercitationem. Iusto ut ut reiciendis sint laudantium et distinctio. Vitae architecto accusamus quos dolores laudantium doloribus alias. Est est esse autem repellat. Assumenda officia aperiam sequi facere distinctio ut. Magnam qui assumenda eligendi sint. Architecto autem harum qui ea quos ut nesciunt et. Optio quidem sit ex quos provident. Et dolor dicta et laudantium. Incidunt id quo enim atque molestiae quam repudiandae omnis. Sed nam voluptatem dolores natus quisquam. Sit nostrum voluptate sed asperiores. Saepe eaque et illum aperiam. Maxime tenetur sunt reiciendis.

Ducimus quia dolorem voluptas ea. Fuga eum architecto eius cum est quibusdam eligendi est. In ut aperiam ea ut.

', views: 143, average_note: 3.1214, commentable: true, @@ -167,8 +187,10 @@ export default { { id: 10, title: 'Totam vel quasi a odio et nihil', - teaser: 'Excepturi veritatis velit rerum nemo voluptatem illum tempora eos. Et impedit sed qui et iusto. A alias asperiores quia quo.', - body: '

Voluptas iure consequatur repudiandae quibusdam iure. Quibusdam consequatur sit cupiditate aut eum iure. Provident ut aut est itaque ut eligendi sunt.

Odio ipsa dolore rem occaecati voluptatum neque. Quia est minima totam est dicta aliquid sed. Doloribus ea eligendi qui odit. Consectetur aut illum aspernatur exercitationem ut. Distinctio sapiente doloribus beatae natus mollitia. Nostrum cum magni autem expedita natus est nulla totam.

Et possimus quia aliquam est molestiae eum. Dicta nostrum ea rerum omnis. Ut hic amet sequi commodi voluptatem ut. Nulla magni totam placeat asperiores error.

', + teaser: + 'Excepturi veritatis velit rerum nemo voluptatem illum tempora eos. Et impedit sed qui et iusto. A alias asperiores quia quo.', + body: + '

Voluptas iure consequatur repudiandae quibusdam iure. Quibusdam consequatur sit cupiditate aut eum iure. Provident ut aut est itaque ut eligendi sunt.

Odio ipsa dolore rem occaecati voluptatum neque. Quia est minima totam est dicta aliquid sed. Doloribus ea eligendi qui odit. Consectetur aut illum aspernatur exercitationem ut. Distinctio sapiente doloribus beatae natus mollitia. Nostrum cum magni autem expedita natus est nulla totam.

Et possimus quia aliquam est molestiae eum. Dicta nostrum ea rerum omnis. Ut hic amet sequi commodi voluptatem ut. Nulla magni totam placeat asperiores error.

', views: 721, average_note: 4.121, commentable: true, @@ -180,8 +202,10 @@ export default { { id: 11, title: 'Omnis voluptate enim similique est possimus', - teaser: 'Velit eos vero reprehenderit ut assumenda saepe qui. Quasi aut laboriosam quas voluptate voluptatem. Et eos officia repudiandae quaerat. Mollitia libero numquam laborum eos.', - body: '

Ut qui a quis culpa impedit. Harum quae sunt aspernatur dolorem minima et dolorum. Consequatur sunt eveniet sit perspiciatis fuga praesentium. Quam voluptatem a ullam accusantium debitis eum consectetur.

Voluptas rem impedit omnis maiores saepe. Eum consequatur ut et consequatur repellat. Quos dolorem dolorum nihil dolor sit optio velit. Quasi quaerat enim omnis ipsum.

Officia asperiores ut doloribus. Architecto iste quia illo non. Deleniti enim odio aut amet eveniet. Modi sint aut excepturi quisquam error sed officia. Nostrum enim repellendus inventore minus. Itaque vitae ipsam quasi. Qui provident vero ab facere. Sit enim provident doloremque minus quam. Voluptatem expedita est maiores nihil est voluptatem error. Asperiores ut a est ducimus hic optio. Natus omnis ullam consectetur ducimus nisi sint ducimus odit. Soluta cupiditate ipsam magnam.

Illum magni aut autem in sed iure. Ea explicabo ducimus officia corrupti ipsam minima minima. Nihil ab similique modi sunt unde nisi. Iusto quis iste ut aut earum magni. Nisi nisi minima sapiente quos aut libero maxime. Ut consequuntur sit vel odio suscipit fugiat tempore et. Et eveniet aut voluptatibus aliquid accusantium quis qui et. Veniam rem ut et. Vel officiis et voluptatum eaque ipsum sit. Sed iste rem ipsam dolor maiores. Et animi aspernatur aut error. Quisquam veritatis voluptatem magnam id. Blanditiis dolorem quo et voluptatum.

', + teaser: + 'Velit eos vero reprehenderit ut assumenda saepe qui. Quasi aut laboriosam quas voluptate voluptatem. Et eos officia repudiandae quaerat. Mollitia libero numquam laborum eos.', + body: + '

Ut qui a quis culpa impedit. Harum quae sunt aspernatur dolorem minima et dolorum. Consequatur sunt eveniet sit perspiciatis fuga praesentium. Quam voluptatem a ullam accusantium debitis eum consectetur.

Voluptas rem impedit omnis maiores saepe. Eum consequatur ut et consequatur repellat. Quos dolorem dolorum nihil dolor sit optio velit. Quasi quaerat enim omnis ipsum.

Officia asperiores ut doloribus. Architecto iste quia illo non. Deleniti enim odio aut amet eveniet. Modi sint aut excepturi quisquam error sed officia. Nostrum enim repellendus inventore minus. Itaque vitae ipsam quasi. Qui provident vero ab facere. Sit enim provident doloremque minus quam. Voluptatem expedita est maiores nihil est voluptatem error. Asperiores ut a est ducimus hic optio. Natus omnis ullam consectetur ducimus nisi sint ducimus odit. Soluta cupiditate ipsam magnam.

Illum magni aut autem in sed iure. Ea explicabo ducimus officia corrupti ipsam minima minima. Nihil ab similique modi sunt unde nisi. Iusto quis iste ut aut earum magni. Nisi nisi minima sapiente quos aut libero maxime. Ut consequuntur sit vel odio suscipit fugiat tempore et. Et eveniet aut voluptatibus aliquid accusantium quis qui et. Veniam rem ut et. Vel officiis et voluptatum eaque ipsum sit. Sed iste rem ipsam dolor maiores. Et animi aspernatur aut error. Quisquam veritatis voluptatem magnam id. Blanditiis dolorem quo et voluptatum.

', views: 294, average_note: 3.12942, commentable: true, @@ -201,8 +225,10 @@ export default { { id: 12, title: 'Qui tempore rerum et voluptates', - teaser: 'Occaecati rem perferendis dolor aut numquam cupiditate. At tenetur dolores pariatur et libero asperiores porro voluptas. Officiis corporis sed eos repellendus perferendis distinctio hic consequatur.', - body: '

Praesentium corrupti minus molestias eveniet mollitia. Sit dolores est tenetur eos veritatis. Vero aut molestias provident ducimus odit optio.

Minima amet accusantium dolores et. Iste eos necessitatibus iure provident rerum repellendus reiciendis eos. Voluptate dolorem dolore aliquid sed maiores.

Ut quia excepturi quidem quidem. Cupiditate qui est rerum praesentium consequatur ad. Minima rem et est. Ut odio nostrum fugit laborum. Quis vitae occaecati tenetur earum non architecto.

Minima est nobis accusamus sunt explicabo fuga. Ut ut ut officia labore ratione animi saepe et.

Accusamus quae ex rerum est eos nesciunt et. Nemo nam consequatur earum necessitatibus et. Eum corporis corporis quia at nihil consectetur accusamus. Ea eveniet et culpa maxime.

Et et quisquam odio sapiente. Voluptas ducimus beatae ratione et soluta esse ut animi. Ipsa architecto veritatis cumque in.

Voluptatem dolore sint aliquam excepturi. Pariatur quisquam a eum. Aut et sit quis et dolorem omnis. Molestias id cupiditate error ab.

Odio ut deleniti incidunt vel dolores eligendi. Nemo aut commodi accusamus alias reprehenderit dolorum eaque. Iure fugit quis occaecati aspernatur tempora iste.

Omnis repellat et sequi numquam accusantium doloribus eum totam. Ab assumenda facere qui voluptate. Temporibus non ipsa officia. Corrupti omnis ut dolores velit aliquam ut omnis consequuntur.

', + teaser: + 'Occaecati rem perferendis dolor aut numquam cupiditate. At tenetur dolores pariatur et libero asperiores porro voluptas. Officiis corporis sed eos repellendus perferendis distinctio hic consequatur.', + body: + '

Praesentium corrupti minus molestias eveniet mollitia. Sit dolores est tenetur eos veritatis. Vero aut molestias provident ducimus odit optio.

Minima amet accusantium dolores et. Iste eos necessitatibus iure provident rerum repellendus reiciendis eos. Voluptate dolorem dolore aliquid sed maiores.

Ut quia excepturi quidem quidem. Cupiditate qui est rerum praesentium consequatur ad. Minima rem et est. Ut odio nostrum fugit laborum. Quis vitae occaecati tenetur earum non architecto.

Minima est nobis accusamus sunt explicabo fuga. Ut ut ut officia labore ratione animi saepe et.

Accusamus quae ex rerum est eos nesciunt et. Nemo nam consequatur earum necessitatibus et. Eum corporis corporis quia at nihil consectetur accusamus. Ea eveniet et culpa maxime.

Et et quisquam odio sapiente. Voluptas ducimus beatae ratione et soluta esse ut animi. Ipsa architecto veritatis cumque in.

Voluptatem dolore sint aliquam excepturi. Pariatur quisquam a eum. Aut et sit quis et dolorem omnis. Molestias id cupiditate error ab.

Odio ut deleniti incidunt vel dolores eligendi. Nemo aut commodi accusamus alias reprehenderit dolorum eaque. Iure fugit quis occaecati aspernatur tempora iste.

Omnis repellat et sequi numquam accusantium doloribus eum totam. Ab assumenda facere qui voluptate. Temporibus non ipsa officia. Corrupti omnis ut dolores velit aliquam ut omnis consequuntur.

', views: 719, average_note: 2, commentable: true, @@ -226,8 +252,10 @@ export default { { id: 13, title: 'Fusce massa lorem, pulvinar a posuere ut, accumsan ac nisi', - teaser: 'Quam earum itaque corrupti labore quas nihil sed. Dolores sunt culpa voluptates exercitationem eveniet totam rerum. Molestias perspiciatis rem numquam accusamus.', - body: '

Curabitur eu odio ullamcorper, pretium sem at, blandit libero. Nulla sodales facilisis libero, eu gravida tellus ultrices nec. In ut gravida mi. Vivamus finibus tortor tempus egestas lacinia. Cras eu arcu nisl. Donec pretium dolor ipsum, eget feugiat urna iaculis ut.

Nullam lacinia accumsan diam, ac faucibus velit maximus ac. Donec eros ligula, ullamcorper sit amet varius eget, molestie nec sapien. Donec ac est non tellus convallis condimentum. Aliquam non vehicula mauris, ac rhoncus mi. Integer consequat ipsum a posuere ornare. Quisque mollis finibus libero scelerisque dapibus.

', + teaser: + 'Quam earum itaque corrupti labore quas nihil sed. Dolores sunt culpa voluptates exercitationem eveniet totam rerum. Molestias perspiciatis rem numquam accusamus.', + body: + '

Curabitur eu odio ullamcorper, pretium sem at, blandit libero. Nulla sodales facilisis libero, eu gravida tellus ultrices nec. In ut gravida mi. Vivamus finibus tortor tempus egestas lacinia. Cras eu arcu nisl. Donec pretium dolor ipsum, eget feugiat urna iaculis ut.

Nullam lacinia accumsan diam, ac faucibus velit maximus ac. Donec eros ligula, ullamcorper sit amet varius eget, molestie nec sapien. Donec ac est non tellus convallis condimentum. Aliquam non vehicula mauris, ac rhoncus mi. Integer consequat ipsum a posuere ornare. Quisque mollis finibus libero scelerisque dapibus.

', views: 222, average_note: 4, commentable: true, @@ -243,7 +271,8 @@ export default { id: 1, author: {}, post_id: 6, - body: "Queen, tossing her head through the wood. 'If it had lost something; and she felt sure it.", + body: + "Queen, tossing her head through the wood. 'If it had lost something; and she felt sure it.", created_at: new Date('2012-08-02'), }, { @@ -253,7 +282,8 @@ export default { email: 'kiley@gmail.com', }, post_id: 9, - body: "White Rabbit: it was indeed: she was out of the ground--and I should frighten them out of its right paw round, 'lives a March Hare. 'Sixteenth,'.", + body: + "White Rabbit: it was indeed: she was out of the ground--and I should frighten them out of its right paw round, 'lives a March Hare. 'Sixteenth,'.", created_at: new Date('2012-08-08'), }, { @@ -262,7 +292,8 @@ export default { name: 'Justina Hegmann', }, post_id: 3, - body: "I'm not Ada,' she said, 'and see whether it's marked \"poison\" or.", + body: + "I'm not Ada,' she said, 'and see whether it's marked \"poison\" or.", created_at: new Date('2012-08-02'), }, { @@ -271,7 +302,8 @@ export default { name: 'Ms. Brionna Smitham MD', }, post_id: 6, - body: "Dormouse. 'Fourteenth of March, I think I can say.' This was such a noise inside, no one else seemed inclined.", + body: + "Dormouse. 'Fourteenth of March, I think I can say.' This was such a noise inside, no one else seemed inclined.", created_at: new Date('2014-09-24'), }, { @@ -280,7 +312,8 @@ export default { name: 'Edmond Schulist', }, post_id: 1, - body: "I ought to tell me your history, you know,' the Hatter and the happy summer days. THE.", + body: + "I ought to tell me your history, you know,' the Hatter and the happy summer days. THE.", created_at: new Date('2012-08-07'), }, { @@ -289,7 +322,8 @@ export default { name: 'Danny Greenholt', }, post_id: 6, - body: 'Duchess asked, with another hedgehog, which seemed to be lost: away went Alice after it, never once considering how in the other. In the very tones of.', + body: + 'Duchess asked, with another hedgehog, which seemed to be lost: away went Alice after it, never once considering how in the other. In the very tones of.', created_at: new Date('2012-08-09'), }, { @@ -298,7 +332,8 @@ export default { name: 'Luciano Berge', }, post_id: 5, - body: "While the Panther were sharing a pie--' [later editions continued as follows.", + body: + "While the Panther were sharing a pie--' [later editions continued as follows.", created_at: new Date('2012-09-06'), }, { @@ -307,7 +342,8 @@ export default { name: 'Annamarie Mayer', }, post_id: 5, - body: "I tell you, you coward!' and at once and put it more clearly,' Alice.", + body: + "I tell you, you coward!' and at once and put it more clearly,' Alice.", created_at: new Date('2012-10-03'), }, { @@ -316,7 +352,8 @@ export default { name: 'Breanna Gibson', }, post_id: 2, - body: "THAT. Then again--\"BEFORE SHE HAD THIS FIT--\" you never tasted an egg!' 'I HAVE tasted eggs, certainly,' said Alice, as she spoke. Alice did not like to have it.", + body: + "THAT. Then again--\"BEFORE SHE HAD THIS FIT--\" you never tasted an egg!' 'I HAVE tasted eggs, certainly,' said Alice, as she spoke. Alice did not like to have it.", created_at: new Date('2012-11-06'), }, { @@ -325,7 +362,8 @@ export default { name: 'Logan Schowalter', }, post_id: 3, - body: "I'd been the whiting,' said the Hatter, it woke up again with a T!' said the Gryphon. '--you advance twice--' 'Each with a growl, And concluded the banquet--] 'What IS the fun?' said.", + body: + "I'd been the whiting,' said the Hatter, it woke up again with a T!' said the Gryphon. '--you advance twice--' 'Each with a growl, And concluded the banquet--] 'What IS the fun?' said.", created_at: new Date('2012-12-07'), }, { @@ -334,7 +372,8 @@ export default { name: 'Logan Schowalter', }, post_id: 1, - body: "I don't want to be?' it asked. 'Oh, I'm not Ada,' she said, 'and see whether it's marked \"poison\" or not'; for she had asked it aloud; and in despair she put her hand on the end of the.", + body: + "I don't want to be?' it asked. 'Oh, I'm not Ada,' she said, 'and see whether it's marked \"poison\" or not'; for she had asked it aloud; and in despair she put her hand on the end of the.", created_at: new Date('2012-08-05'), }, ], diff --git a/example/posts.js b/example/posts.js index 17676251..5113f7a1 100644 --- a/example/posts.js +++ b/example/posts.js @@ -50,38 +50,68 @@ import Chip from 'material-ui/Chip'; export PostIcon from 'material-ui/svg-icons/action/book'; -const QuickFilter = translate(({ label, translate }) => {translate(label)}); +const QuickFilter = translate(({ label, translate }) => + + {translate(label)} + +); -const PostFilter = ({ ...props }) => ( +const PostFilter = ({ ...props }) => - + - - -); + + ; -const titleFieldStyle = { maxWidth: '20em', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }; -export const PostList = ({ ...props }) => ( - } sort={{ field: 'published_at', order: 'DESC' }}> +const titleFieldStyle = { + maxWidth: '20em', + overflow: 'hidden', + textOverflow: 'ellipsis', + whiteSpace: 'nowrap', +}; +export const PostList = ({ ...props }) => + } + sort={{ field: 'published_at', order: 'DESC' }} + > record.title} secondaryText={record => `${record.views} views`} - tertiaryText={record => new Date(record.published_at).toLocaleDateString()} + tertiaryText={record => + new Date(record.published_at).toLocaleDateString()} /> } medium={ - - + + - + @@ -91,21 +121,37 @@ export const PostList = ({ ...props }) => ( } /> - -); + ; -const PostTitle = translate(({ record, translate }) => {record ? translate('post.edit.title', { title: record.title }) : ''}); +const PostTitle = translate(({ record, translate }) => + + {record ? translate('post.edit.title', { title: record.title }) : ''} + +); -const PostCreateToolbar = props => - - -; +const PostCreateToolbar = props => + + + + ; -export const PostCreate = ({ ...props }) => ( +export const PostCreate = ({ ...props }) => - } defaultValue={{ average_note: 0 }} validate={(values) => { + } + defaultValue={{ average_note: 0 }} + validate={values => { const errors = {}; - ['title', 'teaser'].forEach((field) => { + ['title', 'teaser'].forEach(field => { if (!values[field]) { errors[field] = ['Required field']; } @@ -126,12 +172,11 @@ export const PostCreate = ({ ...props }) => ( - -); + ; const emptyKeycode = []; -export const PostEdit = ({ ...props }) => ( +export const PostEdit = ({ ...props }) => } {...props}> @@ -151,25 +196,44 @@ export const PostEdit = ({ ...props }) => ( - + - + - + - + @@ -179,10 +243,9 @@ export const PostEdit = ({ ...props }) => ( - -); +
; -export const PostShow = ({ ...props }) => ( +export const PostShow = ({ ...props }) => } {...props}> @@ -191,7 +254,12 @@ export const PostShow = ({ ...props }) => ( - + @@ -200,16 +268,24 @@ export const PostShow = ({ ...props }) => ( - + - + @@ -219,5 +295,4 @@ export const PostShow = ({ ...props }) => ( - -); + ; diff --git a/example/webpack.config.js b/example/webpack.config.js index 5c320e28..76b3de50 100644 --- a/example/webpack.config.js +++ b/example/webpack.config.js @@ -12,7 +12,7 @@ module.exports = { loaders: [ { test: /\.js$/, exclude: /node_modules/, loader: 'babel-loader' }, { test: /\.css$/, loader: 'style-loader!css-loader' }, - ], + ], }, resolve: { alias: { diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..ed39732f --- /dev/null +++ b/package-lock.json @@ -0,0 +1,8090 @@ +{ + "name": "admin-on-rest", + "version": "1.2.3", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "Base64": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/Base64/-/Base64-0.2.1.tgz", + "integrity": "sha1-ujpCMHCOGGcFBl5mur3Uw1z2ACg=", + "dev": true + }, + "File": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/File/-/File-0.10.2.tgz", + "integrity": "sha1-6Jn3dtJz4iQ7qGEFuzsFbQ+5VgQ=", + "dev": true, + "requires": { + "mime": "1.3.4" + } + }, + "FileList": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/FileList/-/FileList-0.10.2.tgz", + "integrity": "sha1-YAOxqXFZNBZLZ8Q0rWqHQaHNFHo=", + "dev": true + }, + "abbrev": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.0.tgz", + "integrity": "sha1-0FVMIlZjbi9W58LlrRg/hZQo2B8=", + "dev": true + }, + "accepts": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.3.tgz", + "integrity": "sha1-w8p0NJOGSMPg2cHjKN1otiLChMo=", + "dev": true, + "requires": { + "mime-types": "2.1.16", + "negotiator": "0.6.1" + } + }, + "acorn": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.1.1.tgz", + "integrity": "sha512-vOk6uEMctu0vQrvuSqFdJyqj1Q0S5VTDL79qtjo+DhRr+1mmaD+tluFSCZqhvi/JUhXSzoZN2BhtstaPEeE8cw==", + "dev": true + }, + "acorn-jsx": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-3.0.1.tgz", + "integrity": "sha1-r9+UiPsezvyDSPb7IvRk4ypYs2s=", + "dev": true, + "requires": { + "acorn": "3.3.0" + }, + "dependencies": { + "acorn": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz", + "integrity": "sha1-ReN/s56No/JbruP/U2niu18iAXo=", + "dev": true + } + } + }, + "adm-zip": { + "version": "0.4.7", + "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.4.7.tgz", + "integrity": "sha1-hgbCy/HEJs6MjsABdER/1Jtur8E=", + "dev": true + }, + "ajv": { + "version": "4.11.8", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz", + "integrity": "sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY=", + "dev": true, + "requires": { + "co": "4.6.0", + "json-stable-stringify": "1.0.1" + } + }, + "ajv-keywords": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-1.5.1.tgz", + "integrity": "sha1-MU3QpLM2j609/NxU7eYXG4htrzw=", + "dev": true + }, + "align-text": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", + "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=", + "dev": true, + "requires": { + "kind-of": "3.2.2", + "longest": "1.0.1", + "repeat-string": "1.6.1" + } + }, + "alphanum-sort": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/alphanum-sort/-/alphanum-sort-1.0.2.tgz", + "integrity": "sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM=", + "dev": true + }, + "amdefine": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", + "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=", + "dev": true + }, + "ansi-escapes": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-1.4.0.tgz", + "integrity": "sha1-06ioOzGapneTZisT52HHkRQiMG4=", + "dev": true + }, + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "anymatch": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-1.3.2.tgz", + "integrity": "sha512-0XNayC8lTHQ2OI8aljNCN3sSx6hsr/1+rlcDAotXJR7C1oZZHCNsfpbKwMjRA3Uqb5tF1Rae2oloTr4xpq+WjA==", + "dev": true, + "requires": { + "micromatch": "2.3.11", + "normalize-path": "2.1.1" + } + }, + "argparse": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.9.tgz", + "integrity": "sha1-c9g7wmP4bpf4zE9rrhsOkKfSLIY=", + "dev": true, + "requires": { + "sprintf-js": "1.0.3" + } + }, + "aria-query": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-0.3.0.tgz", + "integrity": "sha1-y4qZhOKGJxHIPICt5bj1yg3itGc=", + "dev": true, + "requires": { + "ast-types-flow": "0.0.7" + } + }, + "arr-diff": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", + "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", + "dev": true, + "requires": { + "arr-flatten": "1.1.0" + } + }, + "arr-flatten": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", + "dev": true + }, + "array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=", + "dev": true + }, + "array-union": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", + "dev": true, + "requires": { + "array-uniq": "1.0.3" + } + }, + "array-uniq": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", + "dev": true + }, + "array-unique": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", + "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=", + "dev": true + }, + "array.prototype.find": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/array.prototype.find/-/array.prototype.find-2.0.4.tgz", + "integrity": "sha1-VWpcU2LAhkgyPdrrnenRS8GGTJA=", + "dev": true, + "requires": { + "define-properties": "1.1.2", + "es-abstract": "1.8.0" + } + }, + "arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", + "dev": true + }, + "asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=" + }, + "assert": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/assert/-/assert-1.4.1.tgz", + "integrity": "sha1-mZEtWRg2tab1s0XA8H7vwI/GXZE=", + "dev": true, + "requires": { + "util": "0.10.3" + } + }, + "ast-types-flow": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz", + "integrity": "sha1-9wtzXGvKGlycItmCw+Oef+ujva0=", + "dev": true + }, + "async": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", + "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", + "dev": true + }, + "async-each": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.1.tgz", + "integrity": "sha1-GdOGodntxufByF04iu28xW0zYC0=", + "dev": true + }, + "attr-accept": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-1.1.0.tgz", + "integrity": "sha1-tc01In8WOTWo8d4Q7T66FpQfa+Y=" + }, + "autoprefixer": { + "version": "6.7.7", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-6.7.7.tgz", + "integrity": "sha1-Hb0cg1ZY41zj+ZhAmdsAWFx4IBQ=", + "dev": true, + "requires": { + "browserslist": "1.7.7", + "caniuse-db": "1.0.30000718", + "normalize-range": "0.1.2", + "num2fraction": "1.2.2", + "postcss": "5.2.17", + "postcss-value-parser": "3.3.0" + } + }, + "babel-cli": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-cli/-/babel-cli-6.24.1.tgz", + "integrity": "sha1-IHzXBbumFImy6kG1MSNBz2rKIoM=", + "dev": true, + "requires": { + "babel-core": "6.24.1", + "babel-polyfill": "6.26.0", + "babel-register": "6.24.1", + "babel-runtime": "6.26.0", + "chokidar": "1.7.0", + "commander": "2.11.0", + "convert-source-map": "1.5.0", + "fs-readdir-recursive": "1.0.0", + "glob": "7.1.2", + "lodash": "4.17.4", + "output-file-sync": "1.1.2", + "path-is-absolute": "1.0.1", + "slash": "1.0.0", + "source-map": "0.5.7", + "v8flags": "2.1.1" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "2.5.0", + "regenerator-runtime": "0.11.0" + } + }, + "regenerator-runtime": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.0.tgz", + "integrity": "sha512-/aA0kLeRb5N9K0d4fw7ooEbI+xDe+DKD499EQqygGqeS8N3xto15p09uY2xj7ixP81sNPXvRLnAQIqdVStgb1A==", + "dev": true + } + } + }, + "babel-code-frame": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", + "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", + "dev": true, + "requires": { + "chalk": "1.1.3", + "esutils": "2.0.2", + "js-tokens": "3.0.2" + } + }, + "babel-core": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-6.24.1.tgz", + "integrity": "sha1-jEKFZNzh4fQfszfsNPTDsCK1rYM=", + "dev": true, + "requires": { + "babel-code-frame": "6.26.0", + "babel-generator": "6.26.0", + "babel-helpers": "6.24.1", + "babel-messages": "6.23.0", + "babel-register": "6.24.1", + "babel-runtime": "6.26.0", + "babel-template": "6.26.0", + "babel-traverse": "6.26.0", + "babel-types": "6.26.0", + "babylon": "6.18.0", + "convert-source-map": "1.5.0", + "debug": "2.6.8", + "json5": "0.5.1", + "lodash": "4.17.4", + "minimatch": "3.0.4", + "path-is-absolute": "1.0.1", + "private": "0.1.7", + "slash": "1.0.0", + "source-map": "0.5.7" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "2.5.0", + "regenerator-runtime": "0.11.0" + } + }, + "regenerator-runtime": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.0.tgz", + "integrity": "sha512-/aA0kLeRb5N9K0d4fw7ooEbI+xDe+DKD499EQqygGqeS8N3xto15p09uY2xj7ixP81sNPXvRLnAQIqdVStgb1A==", + "dev": true + } + } + }, + "babel-eslint": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-7.2.3.tgz", + "integrity": "sha1-sv4tgBJkcPXBlELcdXJTqJdxCCc=", + "dev": true, + "requires": { + "babel-code-frame": "6.26.0", + "babel-traverse": "6.26.0", + "babel-types": "6.26.0", + "babylon": "6.18.0" + } + }, + "babel-generator": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.26.0.tgz", + "integrity": "sha1-rBriAHC3n248odMmlhMFN3TyDcU=", + "dev": true, + "requires": { + "babel-messages": "6.23.0", + "babel-runtime": "6.26.0", + "babel-types": "6.26.0", + "detect-indent": "4.0.0", + "jsesc": "1.3.0", + "lodash": "4.17.4", + "source-map": "0.5.7", + "trim-right": "1.0.1" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "2.5.0", + "regenerator-runtime": "0.11.0" + } + }, + "regenerator-runtime": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.0.tgz", + "integrity": "sha512-/aA0kLeRb5N9K0d4fw7ooEbI+xDe+DKD499EQqygGqeS8N3xto15p09uY2xj7ixP81sNPXvRLnAQIqdVStgb1A==", + "dev": true + } + } + }, + "babel-helper-bindify-decorators": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-bindify-decorators/-/babel-helper-bindify-decorators-6.24.1.tgz", + "integrity": "sha1-FMGeXxQte0fxmlJDHlKxzLxAozA=", + "dev": true, + "requires": { + "babel-runtime": "6.26.0", + "babel-traverse": "6.26.0", + "babel-types": "6.26.0" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "2.5.0", + "regenerator-runtime": "0.11.0" + } + }, + "regenerator-runtime": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.0.tgz", + "integrity": "sha512-/aA0kLeRb5N9K0d4fw7ooEbI+xDe+DKD499EQqygGqeS8N3xto15p09uY2xj7ixP81sNPXvRLnAQIqdVStgb1A==", + "dev": true + } + } + }, + "babel-helper-builder-binary-assignment-operator-visitor": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-builder-binary-assignment-operator-visitor/-/babel-helper-builder-binary-assignment-operator-visitor-6.24.1.tgz", + "integrity": "sha1-zORReto1b0IgvK6KAsKzRvmlZmQ=", + "dev": true, + "requires": { + "babel-helper-explode-assignable-expression": "6.24.1", + "babel-runtime": "6.26.0", + "babel-types": "6.26.0" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "2.5.0", + "regenerator-runtime": "0.11.0" + } + }, + "regenerator-runtime": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.0.tgz", + "integrity": "sha512-/aA0kLeRb5N9K0d4fw7ooEbI+xDe+DKD499EQqygGqeS8N3xto15p09uY2xj7ixP81sNPXvRLnAQIqdVStgb1A==", + "dev": true + } + } + }, + "babel-helper-builder-react-jsx": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-helper-builder-react-jsx/-/babel-helper-builder-react-jsx-6.26.0.tgz", + "integrity": "sha1-Of+DE7dci2Xc7/HzHTg+D/KkCKA=", + "dev": true, + "requires": { + "babel-runtime": "6.26.0", + "babel-types": "6.26.0", + "esutils": "2.0.2" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "2.5.0", + "regenerator-runtime": "0.11.0" + } + }, + "regenerator-runtime": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.0.tgz", + "integrity": "sha512-/aA0kLeRb5N9K0d4fw7ooEbI+xDe+DKD499EQqygGqeS8N3xto15p09uY2xj7ixP81sNPXvRLnAQIqdVStgb1A==", + "dev": true + } + } + }, + "babel-helper-call-delegate": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-call-delegate/-/babel-helper-call-delegate-6.24.1.tgz", + "integrity": "sha1-7Oaqzdx25Bw0YfiL/Fdb0Nqi340=", + "dev": true, + "requires": { + "babel-helper-hoist-variables": "6.24.1", + "babel-runtime": "6.26.0", + "babel-traverse": "6.26.0", + "babel-types": "6.26.0" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "2.5.0", + "regenerator-runtime": "0.11.0" + } + }, + "regenerator-runtime": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.0.tgz", + "integrity": "sha512-/aA0kLeRb5N9K0d4fw7ooEbI+xDe+DKD499EQqygGqeS8N3xto15p09uY2xj7ixP81sNPXvRLnAQIqdVStgb1A==", + "dev": true + } + } + }, + "babel-helper-define-map": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-helper-define-map/-/babel-helper-define-map-6.26.0.tgz", + "integrity": "sha1-pfVtq0GiX5fstJjH66ypgZ+Vvl8=", + "dev": true, + "requires": { + "babel-helper-function-name": "6.24.1", + "babel-runtime": "6.26.0", + "babel-types": "6.26.0", + "lodash": "4.17.4" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "2.5.0", + "regenerator-runtime": "0.11.0" + } + }, + "regenerator-runtime": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.0.tgz", + "integrity": "sha512-/aA0kLeRb5N9K0d4fw7ooEbI+xDe+DKD499EQqygGqeS8N3xto15p09uY2xj7ixP81sNPXvRLnAQIqdVStgb1A==", + "dev": true + } + } + }, + "babel-helper-explode-assignable-expression": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-explode-assignable-expression/-/babel-helper-explode-assignable-expression-6.24.1.tgz", + "integrity": "sha1-8luCz33BBDPFX3BZLVdGQArCLKo=", + "dev": true, + "requires": { + "babel-runtime": "6.26.0", + "babel-traverse": "6.26.0", + "babel-types": "6.26.0" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "2.5.0", + "regenerator-runtime": "0.11.0" + } + }, + "regenerator-runtime": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.0.tgz", + "integrity": "sha512-/aA0kLeRb5N9K0d4fw7ooEbI+xDe+DKD499EQqygGqeS8N3xto15p09uY2xj7ixP81sNPXvRLnAQIqdVStgb1A==", + "dev": true + } + } + }, + "babel-helper-explode-class": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-explode-class/-/babel-helper-explode-class-6.24.1.tgz", + "integrity": "sha1-fcKjkQ3uAHBW4eMdZAztPVTqqes=", + "dev": true, + "requires": { + "babel-helper-bindify-decorators": "6.24.1", + "babel-runtime": "6.26.0", + "babel-traverse": "6.26.0", + "babel-types": "6.26.0" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "2.5.0", + "regenerator-runtime": "0.11.0" + } + }, + "regenerator-runtime": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.0.tgz", + "integrity": "sha512-/aA0kLeRb5N9K0d4fw7ooEbI+xDe+DKD499EQqygGqeS8N3xto15p09uY2xj7ixP81sNPXvRLnAQIqdVStgb1A==", + "dev": true + } + } + }, + "babel-helper-function-name": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-function-name/-/babel-helper-function-name-6.24.1.tgz", + "integrity": "sha1-00dbjAPtmCQqJbSDUasYOZ01gKk=", + "dev": true, + "requires": { + "babel-helper-get-function-arity": "6.24.1", + "babel-runtime": "6.26.0", + "babel-template": "6.26.0", + "babel-traverse": "6.26.0", + "babel-types": "6.26.0" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "2.5.0", + "regenerator-runtime": "0.11.0" + } + }, + "regenerator-runtime": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.0.tgz", + "integrity": "sha512-/aA0kLeRb5N9K0d4fw7ooEbI+xDe+DKD499EQqygGqeS8N3xto15p09uY2xj7ixP81sNPXvRLnAQIqdVStgb1A==", + "dev": true + } + } + }, + "babel-helper-get-function-arity": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-get-function-arity/-/babel-helper-get-function-arity-6.24.1.tgz", + "integrity": "sha1-j3eCqpNAfEHTqlCQj4mwMbG2hT0=", + "dev": true, + "requires": { + "babel-runtime": "6.26.0", + "babel-types": "6.26.0" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "2.5.0", + "regenerator-runtime": "0.11.0" + } + }, + "regenerator-runtime": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.0.tgz", + "integrity": "sha512-/aA0kLeRb5N9K0d4fw7ooEbI+xDe+DKD499EQqygGqeS8N3xto15p09uY2xj7ixP81sNPXvRLnAQIqdVStgb1A==", + "dev": true + } + } + }, + "babel-helper-hoist-variables": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-hoist-variables/-/babel-helper-hoist-variables-6.24.1.tgz", + "integrity": "sha1-HssnaJydJVE+rbyZFKc/VAi+enY=", + "dev": true, + "requires": { + "babel-runtime": "6.26.0", + "babel-types": "6.26.0" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "2.5.0", + "regenerator-runtime": "0.11.0" + } + }, + "regenerator-runtime": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.0.tgz", + "integrity": "sha512-/aA0kLeRb5N9K0d4fw7ooEbI+xDe+DKD499EQqygGqeS8N3xto15p09uY2xj7ixP81sNPXvRLnAQIqdVStgb1A==", + "dev": true + } + } + }, + "babel-helper-optimise-call-expression": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-optimise-call-expression/-/babel-helper-optimise-call-expression-6.24.1.tgz", + "integrity": "sha1-96E0J7qfc/j0+pk8VKl4gtEkQlc=", + "dev": true, + "requires": { + "babel-runtime": "6.26.0", + "babel-types": "6.26.0" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "2.5.0", + "regenerator-runtime": "0.11.0" + } + }, + "regenerator-runtime": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.0.tgz", + "integrity": "sha512-/aA0kLeRb5N9K0d4fw7ooEbI+xDe+DKD499EQqygGqeS8N3xto15p09uY2xj7ixP81sNPXvRLnAQIqdVStgb1A==", + "dev": true + } + } + }, + "babel-helper-regex": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-helper-regex/-/babel-helper-regex-6.26.0.tgz", + "integrity": "sha1-MlxZ+QL4LyS3T6zu0DY5VPZJXnI=", + "dev": true, + "requires": { + "babel-runtime": "6.26.0", + "babel-types": "6.26.0", + "lodash": "4.17.4" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "2.5.0", + "regenerator-runtime": "0.11.0" + } + }, + "regenerator-runtime": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.0.tgz", + "integrity": "sha512-/aA0kLeRb5N9K0d4fw7ooEbI+xDe+DKD499EQqygGqeS8N3xto15p09uY2xj7ixP81sNPXvRLnAQIqdVStgb1A==", + "dev": true + } + } + }, + "babel-helper-remap-async-to-generator": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-remap-async-to-generator/-/babel-helper-remap-async-to-generator-6.24.1.tgz", + "integrity": "sha1-XsWBgnrXI/7N04HxySg5BnbkVRs=", + "dev": true, + "requires": { + "babel-helper-function-name": "6.24.1", + "babel-runtime": "6.26.0", + "babel-template": "6.26.0", + "babel-traverse": "6.26.0", + "babel-types": "6.26.0" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "2.5.0", + "regenerator-runtime": "0.11.0" + } + }, + "regenerator-runtime": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.0.tgz", + "integrity": "sha512-/aA0kLeRb5N9K0d4fw7ooEbI+xDe+DKD499EQqygGqeS8N3xto15p09uY2xj7ixP81sNPXvRLnAQIqdVStgb1A==", + "dev": true + } + } + }, + "babel-helper-replace-supers": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-replace-supers/-/babel-helper-replace-supers-6.24.1.tgz", + "integrity": "sha1-v22/5Dk40XNpohPKiov3S2qQqxo=", + "dev": true, + "requires": { + "babel-helper-optimise-call-expression": "6.24.1", + "babel-messages": "6.23.0", + "babel-runtime": "6.26.0", + "babel-template": "6.26.0", + "babel-traverse": "6.26.0", + "babel-types": "6.26.0" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "2.5.0", + "regenerator-runtime": "0.11.0" + } + }, + "regenerator-runtime": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.0.tgz", + "integrity": "sha512-/aA0kLeRb5N9K0d4fw7ooEbI+xDe+DKD499EQqygGqeS8N3xto15p09uY2xj7ixP81sNPXvRLnAQIqdVStgb1A==", + "dev": true + } + } + }, + "babel-helpers": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helpers/-/babel-helpers-6.24.1.tgz", + "integrity": "sha1-NHHenK7DiOXIUOWX5Yom3fN2ArI=", + "dev": true, + "requires": { + "babel-runtime": "6.26.0", + "babel-template": "6.26.0" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "2.5.0", + "regenerator-runtime": "0.11.0" + } + }, + "regenerator-runtime": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.0.tgz", + "integrity": "sha512-/aA0kLeRb5N9K0d4fw7ooEbI+xDe+DKD499EQqygGqeS8N3xto15p09uY2xj7ixP81sNPXvRLnAQIqdVStgb1A==", + "dev": true + } + } + }, + "babel-loader": { + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-6.4.1.tgz", + "integrity": "sha1-CzQRLVsHSKjc2/Uaz2+b1C1QuMo=", + "dev": true, + "requires": { + "find-cache-dir": "0.1.1", + "loader-utils": "0.2.17", + "mkdirp": "0.5.1", + "object-assign": "4.1.1" + } + }, + "babel-messages": { + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.23.0.tgz", + "integrity": "sha1-8830cDhYA1sqKVHG7F7fbGLyYw4=", + "dev": true, + "requires": { + "babel-runtime": "6.26.0" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "2.5.0", + "regenerator-runtime": "0.11.0" + } + }, + "regenerator-runtime": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.0.tgz", + "integrity": "sha512-/aA0kLeRb5N9K0d4fw7ooEbI+xDe+DKD499EQqygGqeS8N3xto15p09uY2xj7ixP81sNPXvRLnAQIqdVStgb1A==", + "dev": true + } + } + }, + "babel-plugin-add-module-exports": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/babel-plugin-add-module-exports/-/babel-plugin-add-module-exports-0.2.1.tgz", + "integrity": "sha1-mumh9KjcZ/DN7E9K7aHkOl/2XiU=", + "dev": true + }, + "babel-plugin-check-es2015-constants": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-check-es2015-constants/-/babel-plugin-check-es2015-constants-6.22.0.tgz", + "integrity": "sha1-NRV7EBQm/S/9PaP3XH0ekYNbv4o=", + "dev": true, + "requires": { + "babel-runtime": "6.26.0" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "2.5.0", + "regenerator-runtime": "0.11.0" + } + }, + "regenerator-runtime": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.0.tgz", + "integrity": "sha512-/aA0kLeRb5N9K0d4fw7ooEbI+xDe+DKD499EQqygGqeS8N3xto15p09uY2xj7ixP81sNPXvRLnAQIqdVStgb1A==", + "dev": true + } + } + }, + "babel-plugin-syntax-async-functions": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-async-functions/-/babel-plugin-syntax-async-functions-6.13.0.tgz", + "integrity": "sha1-ytnK0RkbWtY0vzCuCHI5HgZHvpU=", + "dev": true + }, + "babel-plugin-syntax-async-generators": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-async-generators/-/babel-plugin-syntax-async-generators-6.13.0.tgz", + "integrity": "sha1-a8lj67FuzLrmuStZbrfzXDQqi5o=", + "dev": true + }, + "babel-plugin-syntax-class-constructor-call": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-class-constructor-call/-/babel-plugin-syntax-class-constructor-call-6.18.0.tgz", + "integrity": "sha1-nLnTn+Q8hgC+yBRkVt3L1OGnZBY=", + "dev": true + }, + "babel-plugin-syntax-class-properties": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-class-properties/-/babel-plugin-syntax-class-properties-6.13.0.tgz", + "integrity": "sha1-1+sjt5oxf4VDlixQW4J8fWysJ94=", + "dev": true + }, + "babel-plugin-syntax-decorators": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-decorators/-/babel-plugin-syntax-decorators-6.13.0.tgz", + "integrity": "sha1-MSVjtNvePMgGzuPkFszurd0RrAs=", + "dev": true + }, + "babel-plugin-syntax-do-expressions": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-do-expressions/-/babel-plugin-syntax-do-expressions-6.13.0.tgz", + "integrity": "sha1-V0d1YTmqJtOQ0JQQsDdEugfkeW0=", + "dev": true + }, + "babel-plugin-syntax-dynamic-import": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-dynamic-import/-/babel-plugin-syntax-dynamic-import-6.18.0.tgz", + "integrity": "sha1-jWomIpyDdFqZgqRBBRVyyqF5sdo=", + "dev": true + }, + "babel-plugin-syntax-exponentiation-operator": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-exponentiation-operator/-/babel-plugin-syntax-exponentiation-operator-6.13.0.tgz", + "integrity": "sha1-nufoM3KQ2pUoggGmpX9BcDF4MN4=", + "dev": true + }, + "babel-plugin-syntax-export-extensions": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-export-extensions/-/babel-plugin-syntax-export-extensions-6.13.0.tgz", + "integrity": "sha1-cKFITw+QiaToStRLrDU8lbmxJyE=", + "dev": true + }, + "babel-plugin-syntax-flow": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-flow/-/babel-plugin-syntax-flow-6.18.0.tgz", + "integrity": "sha1-TDqyCiryaqIM0lmVw5jE63AxDI0=", + "dev": true + }, + "babel-plugin-syntax-function-bind": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-function-bind/-/babel-plugin-syntax-function-bind-6.13.0.tgz", + "integrity": "sha1-SMSV8Xe98xqYHnMvVa3AvdJgH0Y=", + "dev": true + }, + "babel-plugin-syntax-jsx": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz", + "integrity": "sha1-CvMqmm4Tyno/1QaeYtew9Y0NiUY=", + "dev": true + }, + "babel-plugin-syntax-object-rest-spread": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz", + "integrity": "sha1-/WU28rzhODb/o6VFjEkDpZe7O/U=", + "dev": true + }, + "babel-plugin-syntax-trailing-function-commas": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-trailing-function-commas/-/babel-plugin-syntax-trailing-function-commas-6.22.0.tgz", + "integrity": "sha1-ugNgk3+NBuQBgKQ/4NVhb/9TLPM=", + "dev": true + }, + "babel-plugin-transform-async-generator-functions": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-async-generator-functions/-/babel-plugin-transform-async-generator-functions-6.24.1.tgz", + "integrity": "sha1-8FiQAUX9PpkHpt3yjaWfIVJYpds=", + "dev": true, + "requires": { + "babel-helper-remap-async-to-generator": "6.24.1", + "babel-plugin-syntax-async-generators": "6.13.0", + "babel-runtime": "6.26.0" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "2.5.0", + "regenerator-runtime": "0.11.0" + } + }, + "regenerator-runtime": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.0.tgz", + "integrity": "sha512-/aA0kLeRb5N9K0d4fw7ooEbI+xDe+DKD499EQqygGqeS8N3xto15p09uY2xj7ixP81sNPXvRLnAQIqdVStgb1A==", + "dev": true + } + } + }, + "babel-plugin-transform-async-to-generator": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-async-to-generator/-/babel-plugin-transform-async-to-generator-6.24.1.tgz", + "integrity": "sha1-ZTbjeK/2yx1VF6wOQOs+n8jQh2E=", + "dev": true, + "requires": { + "babel-helper-remap-async-to-generator": "6.24.1", + "babel-plugin-syntax-async-functions": "6.13.0", + "babel-runtime": "6.26.0" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "2.5.0", + "regenerator-runtime": "0.11.0" + } + }, + "regenerator-runtime": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.0.tgz", + "integrity": "sha512-/aA0kLeRb5N9K0d4fw7ooEbI+xDe+DKD499EQqygGqeS8N3xto15p09uY2xj7ixP81sNPXvRLnAQIqdVStgb1A==", + "dev": true + } + } + }, + "babel-plugin-transform-builtin-extend": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-builtin-extend/-/babel-plugin-transform-builtin-extend-1.1.2.tgz", + "integrity": "sha1-Xpb+z1i4+h7XTvytiEdbKvPJEW4=", + "dev": true, + "requires": { + "babel-runtime": "6.18.0", + "babel-template": "6.26.0" + } + }, + "babel-plugin-transform-class-constructor-call": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-class-constructor-call/-/babel-plugin-transform-class-constructor-call-6.24.1.tgz", + "integrity": "sha1-gNwoVQWsBn3LjWxl4vbxGrd2Xvk=", + "dev": true, + "requires": { + "babel-plugin-syntax-class-constructor-call": "6.18.0", + "babel-runtime": "6.26.0", + "babel-template": "6.26.0" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "2.5.0", + "regenerator-runtime": "0.11.0" + } + }, + "regenerator-runtime": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.0.tgz", + "integrity": "sha512-/aA0kLeRb5N9K0d4fw7ooEbI+xDe+DKD499EQqygGqeS8N3xto15p09uY2xj7ixP81sNPXvRLnAQIqdVStgb1A==", + "dev": true + } + } + }, + "babel-plugin-transform-class-properties": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-class-properties/-/babel-plugin-transform-class-properties-6.24.1.tgz", + "integrity": "sha1-anl2PqYdM9NvN7YRqp3vgagbRqw=", + "dev": true, + "requires": { + "babel-helper-function-name": "6.24.1", + "babel-plugin-syntax-class-properties": "6.13.0", + "babel-runtime": "6.26.0", + "babel-template": "6.26.0" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "2.5.0", + "regenerator-runtime": "0.11.0" + } + }, + "regenerator-runtime": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.0.tgz", + "integrity": "sha512-/aA0kLeRb5N9K0d4fw7ooEbI+xDe+DKD499EQqygGqeS8N3xto15p09uY2xj7ixP81sNPXvRLnAQIqdVStgb1A==", + "dev": true + } + } + }, + "babel-plugin-transform-decorators": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-decorators/-/babel-plugin-transform-decorators-6.24.1.tgz", + "integrity": "sha1-eIAT2PjGtSIr33s0Q5Df13Vp4k0=", + "dev": true, + "requires": { + "babel-helper-explode-class": "6.24.1", + "babel-plugin-syntax-decorators": "6.13.0", + "babel-runtime": "6.26.0", + "babel-template": "6.26.0", + "babel-types": "6.26.0" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "2.5.0", + "regenerator-runtime": "0.11.0" + } + }, + "regenerator-runtime": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.0.tgz", + "integrity": "sha512-/aA0kLeRb5N9K0d4fw7ooEbI+xDe+DKD499EQqygGqeS8N3xto15p09uY2xj7ixP81sNPXvRLnAQIqdVStgb1A==", + "dev": true + } + } + }, + "babel-plugin-transform-do-expressions": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-do-expressions/-/babel-plugin-transform-do-expressions-6.22.0.tgz", + "integrity": "sha1-KMyvkoEtlJws0SgfaQyP3EaK6bs=", + "dev": true, + "requires": { + "babel-plugin-syntax-do-expressions": "6.13.0", + "babel-runtime": "6.26.0" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "2.5.0", + "regenerator-runtime": "0.11.0" + } + }, + "regenerator-runtime": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.0.tgz", + "integrity": "sha512-/aA0kLeRb5N9K0d4fw7ooEbI+xDe+DKD499EQqygGqeS8N3xto15p09uY2xj7ixP81sNPXvRLnAQIqdVStgb1A==", + "dev": true + } + } + }, + "babel-plugin-transform-es2015-arrow-functions": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-arrow-functions/-/babel-plugin-transform-es2015-arrow-functions-6.22.0.tgz", + "integrity": "sha1-RSaSy3EdX3ncf4XkQM5BufJE0iE=", + "dev": true, + "requires": { + "babel-runtime": "6.26.0" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "2.5.0", + "regenerator-runtime": "0.11.0" + } + }, + "regenerator-runtime": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.0.tgz", + "integrity": "sha512-/aA0kLeRb5N9K0d4fw7ooEbI+xDe+DKD499EQqygGqeS8N3xto15p09uY2xj7ixP81sNPXvRLnAQIqdVStgb1A==", + "dev": true + } + } + }, + "babel-plugin-transform-es2015-block-scoped-functions": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-block-scoped-functions/-/babel-plugin-transform-es2015-block-scoped-functions-6.22.0.tgz", + "integrity": "sha1-u8UbSflk1wy42OC5ToICRs46YUE=", + "dev": true, + "requires": { + "babel-runtime": "6.26.0" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "2.5.0", + "regenerator-runtime": "0.11.0" + } + }, + "regenerator-runtime": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.0.tgz", + "integrity": "sha512-/aA0kLeRb5N9K0d4fw7ooEbI+xDe+DKD499EQqygGqeS8N3xto15p09uY2xj7ixP81sNPXvRLnAQIqdVStgb1A==", + "dev": true + } + } + }, + "babel-plugin-transform-es2015-block-scoping": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-block-scoping/-/babel-plugin-transform-es2015-block-scoping-6.26.0.tgz", + "integrity": "sha1-1w9SmcEwjQXBL0Y4E7CgnnOxiV8=", + "dev": true, + "requires": { + "babel-runtime": "6.26.0", + "babel-template": "6.26.0", + "babel-traverse": "6.26.0", + "babel-types": "6.26.0", + "lodash": "4.17.4" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "2.5.0", + "regenerator-runtime": "0.11.0" + } + }, + "regenerator-runtime": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.0.tgz", + "integrity": "sha512-/aA0kLeRb5N9K0d4fw7ooEbI+xDe+DKD499EQqygGqeS8N3xto15p09uY2xj7ixP81sNPXvRLnAQIqdVStgb1A==", + "dev": true + } + } + }, + "babel-plugin-transform-es2015-classes": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-classes/-/babel-plugin-transform-es2015-classes-6.24.1.tgz", + "integrity": "sha1-WkxYpQyclGHlZLSyo7+ryXolhNs=", + "dev": true, + "requires": { + "babel-helper-define-map": "6.26.0", + "babel-helper-function-name": "6.24.1", + "babel-helper-optimise-call-expression": "6.24.1", + "babel-helper-replace-supers": "6.24.1", + "babel-messages": "6.23.0", + "babel-runtime": "6.26.0", + "babel-template": "6.26.0", + "babel-traverse": "6.26.0", + "babel-types": "6.26.0" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "2.5.0", + "regenerator-runtime": "0.11.0" + } + }, + "regenerator-runtime": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.0.tgz", + "integrity": "sha512-/aA0kLeRb5N9K0d4fw7ooEbI+xDe+DKD499EQqygGqeS8N3xto15p09uY2xj7ixP81sNPXvRLnAQIqdVStgb1A==", + "dev": true + } + } + }, + "babel-plugin-transform-es2015-computed-properties": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-computed-properties/-/babel-plugin-transform-es2015-computed-properties-6.24.1.tgz", + "integrity": "sha1-b+Ko0WiV1WNPTNmZttNICjCBWbM=", + "dev": true, + "requires": { + "babel-runtime": "6.26.0", + "babel-template": "6.26.0" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "2.5.0", + "regenerator-runtime": "0.11.0" + } + }, + "regenerator-runtime": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.0.tgz", + "integrity": "sha512-/aA0kLeRb5N9K0d4fw7ooEbI+xDe+DKD499EQqygGqeS8N3xto15p09uY2xj7ixP81sNPXvRLnAQIqdVStgb1A==", + "dev": true + } + } + }, + "babel-plugin-transform-es2015-destructuring": { + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-destructuring/-/babel-plugin-transform-es2015-destructuring-6.23.0.tgz", + "integrity": "sha1-mXux8auWf2gtKwh2/jWNYOdlxW0=", + "dev": true, + "requires": { + "babel-runtime": "6.26.0" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "2.5.0", + "regenerator-runtime": "0.11.0" + } + }, + "regenerator-runtime": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.0.tgz", + "integrity": "sha512-/aA0kLeRb5N9K0d4fw7ooEbI+xDe+DKD499EQqygGqeS8N3xto15p09uY2xj7ixP81sNPXvRLnAQIqdVStgb1A==", + "dev": true + } + } + }, + "babel-plugin-transform-es2015-duplicate-keys": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-duplicate-keys/-/babel-plugin-transform-es2015-duplicate-keys-6.24.1.tgz", + "integrity": "sha1-c+s9MQypaePvnskcU3QabxV2Qj4=", + "dev": true, + "requires": { + "babel-runtime": "6.26.0", + "babel-types": "6.26.0" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "2.5.0", + "regenerator-runtime": "0.11.0" + } + }, + "regenerator-runtime": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.0.tgz", + "integrity": "sha512-/aA0kLeRb5N9K0d4fw7ooEbI+xDe+DKD499EQqygGqeS8N3xto15p09uY2xj7ixP81sNPXvRLnAQIqdVStgb1A==", + "dev": true + } + } + }, + "babel-plugin-transform-es2015-for-of": { + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-for-of/-/babel-plugin-transform-es2015-for-of-6.23.0.tgz", + "integrity": "sha1-9HyVsrYT3x0+zC/bdXNiPHUkhpE=", + "dev": true, + "requires": { + "babel-runtime": "6.26.0" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "2.5.0", + "regenerator-runtime": "0.11.0" + } + }, + "regenerator-runtime": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.0.tgz", + "integrity": "sha512-/aA0kLeRb5N9K0d4fw7ooEbI+xDe+DKD499EQqygGqeS8N3xto15p09uY2xj7ixP81sNPXvRLnAQIqdVStgb1A==", + "dev": true + } + } + }, + "babel-plugin-transform-es2015-function-name": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-function-name/-/babel-plugin-transform-es2015-function-name-6.24.1.tgz", + "integrity": "sha1-g0yJhTvDaxrw86TF26qU/Y6sqos=", + "dev": true, + "requires": { + "babel-helper-function-name": "6.24.1", + "babel-runtime": "6.26.0", + "babel-types": "6.26.0" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "2.5.0", + "regenerator-runtime": "0.11.0" + } + }, + "regenerator-runtime": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.0.tgz", + "integrity": "sha512-/aA0kLeRb5N9K0d4fw7ooEbI+xDe+DKD499EQqygGqeS8N3xto15p09uY2xj7ixP81sNPXvRLnAQIqdVStgb1A==", + "dev": true + } + } + }, + "babel-plugin-transform-es2015-literals": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-literals/-/babel-plugin-transform-es2015-literals-6.22.0.tgz", + "integrity": "sha1-T1SgLWzWbPkVKAAZox0xklN3yi4=", + "dev": true, + "requires": { + "babel-runtime": "6.26.0" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "2.5.0", + "regenerator-runtime": "0.11.0" + } + }, + "regenerator-runtime": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.0.tgz", + "integrity": "sha512-/aA0kLeRb5N9K0d4fw7ooEbI+xDe+DKD499EQqygGqeS8N3xto15p09uY2xj7ixP81sNPXvRLnAQIqdVStgb1A==", + "dev": true + } + } + }, + "babel-plugin-transform-es2015-modules-amd": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-amd/-/babel-plugin-transform-es2015-modules-amd-6.24.1.tgz", + "integrity": "sha1-Oz5UAXI5hC1tGcMBHEvS8AoA0VQ=", + "dev": true, + "requires": { + "babel-plugin-transform-es2015-modules-commonjs": "6.26.0", + "babel-runtime": "6.26.0", + "babel-template": "6.26.0" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "2.5.0", + "regenerator-runtime": "0.11.0" + } + }, + "regenerator-runtime": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.0.tgz", + "integrity": "sha512-/aA0kLeRb5N9K0d4fw7ooEbI+xDe+DKD499EQqygGqeS8N3xto15p09uY2xj7ixP81sNPXvRLnAQIqdVStgb1A==", + "dev": true + } + } + }, + "babel-plugin-transform-es2015-modules-commonjs": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.26.0.tgz", + "integrity": "sha1-DYOUApt9xqvhqX7xgeAHWN0uXYo=", + "dev": true, + "requires": { + "babel-plugin-transform-strict-mode": "6.24.1", + "babel-runtime": "6.26.0", + "babel-template": "6.26.0", + "babel-types": "6.26.0" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "2.5.0", + "regenerator-runtime": "0.11.0" + } + }, + "regenerator-runtime": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.0.tgz", + "integrity": "sha512-/aA0kLeRb5N9K0d4fw7ooEbI+xDe+DKD499EQqygGqeS8N3xto15p09uY2xj7ixP81sNPXvRLnAQIqdVStgb1A==", + "dev": true + } + } + }, + "babel-plugin-transform-es2015-modules-systemjs": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-systemjs/-/babel-plugin-transform-es2015-modules-systemjs-6.24.1.tgz", + "integrity": "sha1-/4mhQrkRmpBhlfXxBuzzBdlAfSM=", + "dev": true, + "requires": { + "babel-helper-hoist-variables": "6.24.1", + "babel-runtime": "6.26.0", + "babel-template": "6.26.0" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "2.5.0", + "regenerator-runtime": "0.11.0" + } + }, + "regenerator-runtime": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.0.tgz", + "integrity": "sha512-/aA0kLeRb5N9K0d4fw7ooEbI+xDe+DKD499EQqygGqeS8N3xto15p09uY2xj7ixP81sNPXvRLnAQIqdVStgb1A==", + "dev": true + } + } + }, + "babel-plugin-transform-es2015-modules-umd": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-umd/-/babel-plugin-transform-es2015-modules-umd-6.24.1.tgz", + "integrity": "sha1-rJl+YoXNGO1hdq22B9YCNErThGg=", + "dev": true, + "requires": { + "babel-plugin-transform-es2015-modules-amd": "6.24.1", + "babel-runtime": "6.26.0", + "babel-template": "6.26.0" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "2.5.0", + "regenerator-runtime": "0.11.0" + } + }, + "regenerator-runtime": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.0.tgz", + "integrity": "sha512-/aA0kLeRb5N9K0d4fw7ooEbI+xDe+DKD499EQqygGqeS8N3xto15p09uY2xj7ixP81sNPXvRLnAQIqdVStgb1A==", + "dev": true + } + } + }, + "babel-plugin-transform-es2015-object-super": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-object-super/-/babel-plugin-transform-es2015-object-super-6.24.1.tgz", + "integrity": "sha1-JM72muIcuDp/hgPa0CH1cusnj40=", + "dev": true, + "requires": { + "babel-helper-replace-supers": "6.24.1", + "babel-runtime": "6.26.0" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "2.5.0", + "regenerator-runtime": "0.11.0" + } + }, + "regenerator-runtime": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.0.tgz", + "integrity": "sha512-/aA0kLeRb5N9K0d4fw7ooEbI+xDe+DKD499EQqygGqeS8N3xto15p09uY2xj7ixP81sNPXvRLnAQIqdVStgb1A==", + "dev": true + } + } + }, + "babel-plugin-transform-es2015-parameters": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-parameters/-/babel-plugin-transform-es2015-parameters-6.24.1.tgz", + "integrity": "sha1-V6w1GrScrxSpfNE7CfZv3wpiXys=", + "dev": true, + "requires": { + "babel-helper-call-delegate": "6.24.1", + "babel-helper-get-function-arity": "6.24.1", + "babel-runtime": "6.26.0", + "babel-template": "6.26.0", + "babel-traverse": "6.26.0", + "babel-types": "6.26.0" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "2.5.0", + "regenerator-runtime": "0.11.0" + } + }, + "regenerator-runtime": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.0.tgz", + "integrity": "sha512-/aA0kLeRb5N9K0d4fw7ooEbI+xDe+DKD499EQqygGqeS8N3xto15p09uY2xj7ixP81sNPXvRLnAQIqdVStgb1A==", + "dev": true + } + } + }, + "babel-plugin-transform-es2015-shorthand-properties": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-shorthand-properties/-/babel-plugin-transform-es2015-shorthand-properties-6.24.1.tgz", + "integrity": "sha1-JPh11nIch2YbvZmkYi5R8U3jiqA=", + "dev": true, + "requires": { + "babel-runtime": "6.26.0", + "babel-types": "6.26.0" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "2.5.0", + "regenerator-runtime": "0.11.0" + } + }, + "regenerator-runtime": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.0.tgz", + "integrity": "sha512-/aA0kLeRb5N9K0d4fw7ooEbI+xDe+DKD499EQqygGqeS8N3xto15p09uY2xj7ixP81sNPXvRLnAQIqdVStgb1A==", + "dev": true + } + } + }, + "babel-plugin-transform-es2015-spread": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-spread/-/babel-plugin-transform-es2015-spread-6.22.0.tgz", + "integrity": "sha1-1taKmfia7cRTbIGlQujdnxdG+NE=", + "dev": true, + "requires": { + "babel-runtime": "6.26.0" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "2.5.0", + "regenerator-runtime": "0.11.0" + } + }, + "regenerator-runtime": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.0.tgz", + "integrity": "sha512-/aA0kLeRb5N9K0d4fw7ooEbI+xDe+DKD499EQqygGqeS8N3xto15p09uY2xj7ixP81sNPXvRLnAQIqdVStgb1A==", + "dev": true + } + } + }, + "babel-plugin-transform-es2015-sticky-regex": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-sticky-regex/-/babel-plugin-transform-es2015-sticky-regex-6.24.1.tgz", + "integrity": "sha1-AMHNsaynERLN8M9hJsLta0V8zbw=", + "dev": true, + "requires": { + "babel-helper-regex": "6.26.0", + "babel-runtime": "6.26.0", + "babel-types": "6.26.0" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "2.5.0", + "regenerator-runtime": "0.11.0" + } + }, + "regenerator-runtime": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.0.tgz", + "integrity": "sha512-/aA0kLeRb5N9K0d4fw7ooEbI+xDe+DKD499EQqygGqeS8N3xto15p09uY2xj7ixP81sNPXvRLnAQIqdVStgb1A==", + "dev": true + } + } + }, + "babel-plugin-transform-es2015-template-literals": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-template-literals/-/babel-plugin-transform-es2015-template-literals-6.22.0.tgz", + "integrity": "sha1-qEs0UPfp+PH2g51taH2oS7EjbY0=", + "dev": true, + "requires": { + "babel-runtime": "6.26.0" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "2.5.0", + "regenerator-runtime": "0.11.0" + } + }, + "regenerator-runtime": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.0.tgz", + "integrity": "sha512-/aA0kLeRb5N9K0d4fw7ooEbI+xDe+DKD499EQqygGqeS8N3xto15p09uY2xj7ixP81sNPXvRLnAQIqdVStgb1A==", + "dev": true + } + } + }, + "babel-plugin-transform-es2015-typeof-symbol": { + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-typeof-symbol/-/babel-plugin-transform-es2015-typeof-symbol-6.23.0.tgz", + "integrity": "sha1-3sCfHN3/lLUqxz1QXITfWdzOs3I=", + "dev": true, + "requires": { + "babel-runtime": "6.26.0" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "2.5.0", + "regenerator-runtime": "0.11.0" + } + }, + "regenerator-runtime": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.0.tgz", + "integrity": "sha512-/aA0kLeRb5N9K0d4fw7ooEbI+xDe+DKD499EQqygGqeS8N3xto15p09uY2xj7ixP81sNPXvRLnAQIqdVStgb1A==", + "dev": true + } + } + }, + "babel-plugin-transform-es2015-unicode-regex": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-unicode-regex/-/babel-plugin-transform-es2015-unicode-regex-6.24.1.tgz", + "integrity": "sha1-04sS9C6nMj9yk4fxinxa4frrNek=", + "dev": true, + "requires": { + "babel-helper-regex": "6.26.0", + "babel-runtime": "6.26.0", + "regexpu-core": "2.0.0" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "2.5.0", + "regenerator-runtime": "0.11.0" + } + }, + "regenerator-runtime": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.0.tgz", + "integrity": "sha512-/aA0kLeRb5N9K0d4fw7ooEbI+xDe+DKD499EQqygGqeS8N3xto15p09uY2xj7ixP81sNPXvRLnAQIqdVStgb1A==", + "dev": true + } + } + }, + "babel-plugin-transform-exponentiation-operator": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-exponentiation-operator/-/babel-plugin-transform-exponentiation-operator-6.24.1.tgz", + "integrity": "sha1-KrDJx/MJj6SJB3cruBP+QejeOg4=", + "dev": true, + "requires": { + "babel-helper-builder-binary-assignment-operator-visitor": "6.24.1", + "babel-plugin-syntax-exponentiation-operator": "6.13.0", + "babel-runtime": "6.26.0" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "2.5.0", + "regenerator-runtime": "0.11.0" + } + }, + "regenerator-runtime": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.0.tgz", + "integrity": "sha512-/aA0kLeRb5N9K0d4fw7ooEbI+xDe+DKD499EQqygGqeS8N3xto15p09uY2xj7ixP81sNPXvRLnAQIqdVStgb1A==", + "dev": true + } + } + }, + "babel-plugin-transform-export-extensions": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-export-extensions/-/babel-plugin-transform-export-extensions-6.22.0.tgz", + "integrity": "sha1-U3OLR+deghhYnuqUbLvTkQm75lM=", + "dev": true, + "requires": { + "babel-plugin-syntax-export-extensions": "6.13.0", + "babel-runtime": "6.26.0" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "2.5.0", + "regenerator-runtime": "0.11.0" + } + }, + "regenerator-runtime": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.0.tgz", + "integrity": "sha512-/aA0kLeRb5N9K0d4fw7ooEbI+xDe+DKD499EQqygGqeS8N3xto15p09uY2xj7ixP81sNPXvRLnAQIqdVStgb1A==", + "dev": true + } + } + }, + "babel-plugin-transform-flow-strip-types": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-flow-strip-types/-/babel-plugin-transform-flow-strip-types-6.22.0.tgz", + "integrity": "sha1-hMtnKTXUNxT9wyvOhFaNh0Qc988=", + "dev": true, + "requires": { + "babel-plugin-syntax-flow": "6.18.0", + "babel-runtime": "6.26.0" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "2.5.0", + "regenerator-runtime": "0.11.0" + } + }, + "regenerator-runtime": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.0.tgz", + "integrity": "sha512-/aA0kLeRb5N9K0d4fw7ooEbI+xDe+DKD499EQqygGqeS8N3xto15p09uY2xj7ixP81sNPXvRLnAQIqdVStgb1A==", + "dev": true + } + } + }, + "babel-plugin-transform-function-bind": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-function-bind/-/babel-plugin-transform-function-bind-6.22.0.tgz", + "integrity": "sha1-xvuOlqwpajELjPjqQBRiQH3fapc=", + "dev": true, + "requires": { + "babel-plugin-syntax-function-bind": "6.13.0", + "babel-runtime": "6.26.0" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "2.5.0", + "regenerator-runtime": "0.11.0" + } + }, + "regenerator-runtime": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.0.tgz", + "integrity": "sha512-/aA0kLeRb5N9K0d4fw7ooEbI+xDe+DKD499EQqygGqeS8N3xto15p09uY2xj7ixP81sNPXvRLnAQIqdVStgb1A==", + "dev": true + } + } + }, + "babel-plugin-transform-object-rest-spread": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-object-rest-spread/-/babel-plugin-transform-object-rest-spread-6.26.0.tgz", + "integrity": "sha1-DzZpLVD+9rfi1LOsFHgTepY7ewY=", + "dev": true, + "requires": { + "babel-plugin-syntax-object-rest-spread": "6.13.0", + "babel-runtime": "6.26.0" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "2.5.0", + "regenerator-runtime": "0.11.0" + } + }, + "regenerator-runtime": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.0.tgz", + "integrity": "sha512-/aA0kLeRb5N9K0d4fw7ooEbI+xDe+DKD499EQqygGqeS8N3xto15p09uY2xj7ixP81sNPXvRLnAQIqdVStgb1A==", + "dev": true + } + } + }, + "babel-plugin-transform-react-display-name": { + "version": "6.25.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-react-display-name/-/babel-plugin-transform-react-display-name-6.25.0.tgz", + "integrity": "sha1-Z+K/Hx6ck6sI25Z5LgU5K/LMKNE=", + "dev": true, + "requires": { + "babel-runtime": "6.26.0" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "2.5.0", + "regenerator-runtime": "0.11.0" + } + }, + "regenerator-runtime": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.0.tgz", + "integrity": "sha512-/aA0kLeRb5N9K0d4fw7ooEbI+xDe+DKD499EQqygGqeS8N3xto15p09uY2xj7ixP81sNPXvRLnAQIqdVStgb1A==", + "dev": true + } + } + }, + "babel-plugin-transform-react-jsx": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-react-jsx/-/babel-plugin-transform-react-jsx-6.24.1.tgz", + "integrity": "sha1-hAoCjn30YN/DotKfDA2R9jduZqM=", + "dev": true, + "requires": { + "babel-helper-builder-react-jsx": "6.26.0", + "babel-plugin-syntax-jsx": "6.18.0", + "babel-runtime": "6.26.0" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "2.5.0", + "regenerator-runtime": "0.11.0" + } + }, + "regenerator-runtime": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.0.tgz", + "integrity": "sha512-/aA0kLeRb5N9K0d4fw7ooEbI+xDe+DKD499EQqygGqeS8N3xto15p09uY2xj7ixP81sNPXvRLnAQIqdVStgb1A==", + "dev": true + } + } + }, + "babel-plugin-transform-react-jsx-self": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-react-jsx-self/-/babel-plugin-transform-react-jsx-self-6.22.0.tgz", + "integrity": "sha1-322AqdomEqEh5t3XVYvL7PBuY24=", + "dev": true, + "requires": { + "babel-plugin-syntax-jsx": "6.18.0", + "babel-runtime": "6.26.0" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "2.5.0", + "regenerator-runtime": "0.11.0" + } + }, + "regenerator-runtime": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.0.tgz", + "integrity": "sha512-/aA0kLeRb5N9K0d4fw7ooEbI+xDe+DKD499EQqygGqeS8N3xto15p09uY2xj7ixP81sNPXvRLnAQIqdVStgb1A==", + "dev": true + } + } + }, + "babel-plugin-transform-react-jsx-source": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-react-jsx-source/-/babel-plugin-transform-react-jsx-source-6.22.0.tgz", + "integrity": "sha1-ZqwSFT9c0tF7PBkmj0vwGX9E7NY=", + "dev": true, + "requires": { + "babel-plugin-syntax-jsx": "6.18.0", + "babel-runtime": "6.26.0" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "2.5.0", + "regenerator-runtime": "0.11.0" + } + }, + "regenerator-runtime": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.0.tgz", + "integrity": "sha512-/aA0kLeRb5N9K0d4fw7ooEbI+xDe+DKD499EQqygGqeS8N3xto15p09uY2xj7ixP81sNPXvRLnAQIqdVStgb1A==", + "dev": true + } + } + }, + "babel-plugin-transform-regenerator": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-regenerator/-/babel-plugin-transform-regenerator-6.26.0.tgz", + "integrity": "sha1-4HA2lvveJ/Cj78rPi03KL3s6jy8=", + "dev": true, + "requires": { + "regenerator-transform": "0.10.1" + } + }, + "babel-plugin-transform-runtime": { + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-runtime/-/babel-plugin-transform-runtime-6.23.0.tgz", + "integrity": "sha1-iEkNRGUC6puOfvsP4J7E2ZR5se4=", + "dev": true, + "requires": { + "babel-runtime": "6.26.0" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "2.5.0", + "regenerator-runtime": "0.11.0" + } + }, + "regenerator-runtime": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.0.tgz", + "integrity": "sha512-/aA0kLeRb5N9K0d4fw7ooEbI+xDe+DKD499EQqygGqeS8N3xto15p09uY2xj7ixP81sNPXvRLnAQIqdVStgb1A==", + "dev": true + } + } + }, + "babel-plugin-transform-strict-mode": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-strict-mode/-/babel-plugin-transform-strict-mode-6.24.1.tgz", + "integrity": "sha1-1fr3qleKZbvlkc9e2uBKDGcCB1g=", + "dev": true, + "requires": { + "babel-runtime": "6.26.0", + "babel-types": "6.26.0" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "2.5.0", + "regenerator-runtime": "0.11.0" + } + }, + "regenerator-runtime": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.0.tgz", + "integrity": "sha512-/aA0kLeRb5N9K0d4fw7ooEbI+xDe+DKD499EQqygGqeS8N3xto15p09uY2xj7ixP81sNPXvRLnAQIqdVStgb1A==", + "dev": true + } + } + }, + "babel-polyfill": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-polyfill/-/babel-polyfill-6.26.0.tgz", + "integrity": "sha1-N5k3q8Z9eJWXCtxiHyhM2WbPIVM=", + "dev": true, + "requires": { + "babel-runtime": "6.26.0", + "core-js": "2.5.0", + "regenerator-runtime": "0.10.5" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "2.5.0", + "regenerator-runtime": "0.11.0" + }, + "dependencies": { + "regenerator-runtime": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.0.tgz", + "integrity": "sha512-/aA0kLeRb5N9K0d4fw7ooEbI+xDe+DKD499EQqygGqeS8N3xto15p09uY2xj7ixP81sNPXvRLnAQIqdVStgb1A==", + "dev": true + } + } + }, + "regenerator-runtime": { + "version": "0.10.5", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz", + "integrity": "sha1-M2w+/BIgrc7dosn6tntaeVWjNlg=", + "dev": true + } + } + }, + "babel-preset-es2015": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-preset-es2015/-/babel-preset-es2015-6.24.1.tgz", + "integrity": "sha1-1EBQ1rwsn+6nAqrzjXJ6AhBTiTk=", + "dev": true, + "requires": { + "babel-plugin-check-es2015-constants": "6.22.0", + "babel-plugin-transform-es2015-arrow-functions": "6.22.0", + "babel-plugin-transform-es2015-block-scoped-functions": "6.22.0", + "babel-plugin-transform-es2015-block-scoping": "6.26.0", + "babel-plugin-transform-es2015-classes": "6.24.1", + "babel-plugin-transform-es2015-computed-properties": "6.24.1", + "babel-plugin-transform-es2015-destructuring": "6.23.0", + "babel-plugin-transform-es2015-duplicate-keys": "6.24.1", + "babel-plugin-transform-es2015-for-of": "6.23.0", + "babel-plugin-transform-es2015-function-name": "6.24.1", + "babel-plugin-transform-es2015-literals": "6.22.0", + "babel-plugin-transform-es2015-modules-amd": "6.24.1", + "babel-plugin-transform-es2015-modules-commonjs": "6.26.0", + "babel-plugin-transform-es2015-modules-systemjs": "6.24.1", + "babel-plugin-transform-es2015-modules-umd": "6.24.1", + "babel-plugin-transform-es2015-object-super": "6.24.1", + "babel-plugin-transform-es2015-parameters": "6.24.1", + "babel-plugin-transform-es2015-shorthand-properties": "6.24.1", + "babel-plugin-transform-es2015-spread": "6.22.0", + "babel-plugin-transform-es2015-sticky-regex": "6.24.1", + "babel-plugin-transform-es2015-template-literals": "6.22.0", + "babel-plugin-transform-es2015-typeof-symbol": "6.23.0", + "babel-plugin-transform-es2015-unicode-regex": "6.24.1", + "babel-plugin-transform-regenerator": "6.26.0" + } + }, + "babel-preset-flow": { + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/babel-preset-flow/-/babel-preset-flow-6.23.0.tgz", + "integrity": "sha1-5xIYiHCFrpoktb5Baa/7WZgWxJ0=", + "dev": true, + "requires": { + "babel-plugin-transform-flow-strip-types": "6.22.0" + } + }, + "babel-preset-react": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-preset-react/-/babel-preset-react-6.24.1.tgz", + "integrity": "sha1-umnfrqRfw+xjm2pOzqbhdwLJE4A=", + "dev": true, + "requires": { + "babel-plugin-syntax-jsx": "6.18.0", + "babel-plugin-transform-react-display-name": "6.25.0", + "babel-plugin-transform-react-jsx": "6.24.1", + "babel-plugin-transform-react-jsx-self": "6.22.0", + "babel-plugin-transform-react-jsx-source": "6.22.0", + "babel-preset-flow": "6.23.0" + } + }, + "babel-preset-stage-0": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-preset-stage-0/-/babel-preset-stage-0-6.24.1.tgz", + "integrity": "sha1-VkLRUEL5E4TX5a+LyIsduVsDnmo=", + "dev": true, + "requires": { + "babel-plugin-transform-do-expressions": "6.22.0", + "babel-plugin-transform-function-bind": "6.22.0", + "babel-preset-stage-1": "6.24.1" + } + }, + "babel-preset-stage-1": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-preset-stage-1/-/babel-preset-stage-1-6.24.1.tgz", + "integrity": "sha1-dpLNfc1oSZB+auSgqFWJz7niv7A=", + "dev": true, + "requires": { + "babel-plugin-transform-class-constructor-call": "6.24.1", + "babel-plugin-transform-export-extensions": "6.22.0", + "babel-preset-stage-2": "6.24.1" + } + }, + "babel-preset-stage-2": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-preset-stage-2/-/babel-preset-stage-2-6.24.1.tgz", + "integrity": "sha1-2eKWD7PXEYfw5k7sYrwHdnIZvcE=", + "dev": true, + "requires": { + "babel-plugin-syntax-dynamic-import": "6.18.0", + "babel-plugin-transform-class-properties": "6.24.1", + "babel-plugin-transform-decorators": "6.24.1", + "babel-preset-stage-3": "6.24.1" + } + }, + "babel-preset-stage-3": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-preset-stage-3/-/babel-preset-stage-3-6.24.1.tgz", + "integrity": "sha1-g2raCp56f6N8sTj7kyb4eTSkg5U=", + "dev": true, + "requires": { + "babel-plugin-syntax-trailing-function-commas": "6.22.0", + "babel-plugin-transform-async-generator-functions": "6.24.1", + "babel-plugin-transform-async-to-generator": "6.24.1", + "babel-plugin-transform-exponentiation-operator": "6.24.1", + "babel-plugin-transform-object-rest-spread": "6.26.0" + } + }, + "babel-register": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-register/-/babel-register-6.24.1.tgz", + "integrity": "sha1-fhDhOi9xBlvfrVoXh7pFvKbe118=", + "dev": true, + "requires": { + "babel-core": "6.24.1", + "babel-runtime": "6.26.0", + "core-js": "2.5.0", + "home-or-tmp": "2.0.0", + "lodash": "4.17.4", + "mkdirp": "0.5.1", + "source-map-support": "0.4.16" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "2.5.0", + "regenerator-runtime": "0.11.0" + } + }, + "regenerator-runtime": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.0.tgz", + "integrity": "sha512-/aA0kLeRb5N9K0d4fw7ooEbI+xDe+DKD499EQqygGqeS8N3xto15p09uY2xj7ixP81sNPXvRLnAQIqdVStgb1A==", + "dev": true + } + } + }, + "babel-runtime": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.18.0.tgz", + "integrity": "sha1-D0F3/9mEku8Tufgj6ZlKAlhMkHg=", + "requires": { + "core-js": "2.5.0", + "regenerator-runtime": "0.9.6" + } + }, + "babel-template": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.26.0.tgz", + "integrity": "sha1-3gPi0WOWsGn0bdn/+FIfsaDjXgI=", + "dev": true, + "requires": { + "babel-runtime": "6.26.0", + "babel-traverse": "6.26.0", + "babel-types": "6.26.0", + "babylon": "6.18.0", + "lodash": "4.17.4" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "2.5.0", + "regenerator-runtime": "0.11.0" + } + }, + "regenerator-runtime": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.0.tgz", + "integrity": "sha512-/aA0kLeRb5N9K0d4fw7ooEbI+xDe+DKD499EQqygGqeS8N3xto15p09uY2xj7ixP81sNPXvRLnAQIqdVStgb1A==", + "dev": true + } + } + }, + "babel-traverse": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.26.0.tgz", + "integrity": "sha1-RqnL1+3MYsjlwGTi0tjQ9ANXZu4=", + "dev": true, + "requires": { + "babel-code-frame": "6.26.0", + "babel-messages": "6.23.0", + "babel-runtime": "6.26.0", + "babel-types": "6.26.0", + "babylon": "6.18.0", + "debug": "2.6.8", + "globals": "9.18.0", + "invariant": "2.2.2", + "lodash": "4.17.4" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "2.5.0", + "regenerator-runtime": "0.11.0" + } + }, + "regenerator-runtime": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.0.tgz", + "integrity": "sha512-/aA0kLeRb5N9K0d4fw7ooEbI+xDe+DKD499EQqygGqeS8N3xto15p09uY2xj7ixP81sNPXvRLnAQIqdVStgb1A==", + "dev": true + } + } + }, + "babel-types": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", + "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=", + "dev": true, + "requires": { + "babel-runtime": "6.26.0", + "esutils": "2.0.2", + "lodash": "4.17.4", + "to-fast-properties": "1.0.3" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "2.5.0", + "regenerator-runtime": "0.11.0" + } + }, + "regenerator-runtime": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.0.tgz", + "integrity": "sha512-/aA0kLeRb5N9K0d4fw7ooEbI+xDe+DKD499EQqygGqeS8N3xto15p09uY2xj7ixP81sNPXvRLnAQIqdVStgb1A==", + "dev": true + } + } + }, + "babylon": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", + "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==", + "dev": true + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "base64-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.2.1.tgz", + "integrity": "sha512-dwVUVIXsBZXwTuwnXI9RK8sBmgq09NDHzyR9SAph9eqk76gKK2JSQmZARC2zRC81JC2QTtxD0ARU5qTS25gIGw==", + "dev": true + }, + "batch": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", + "integrity": "sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY=", + "dev": true + }, + "big.js": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-3.1.3.tgz", + "integrity": "sha1-TK2iGTZS6zyp7I5VyQFWacmAaXg=", + "dev": true + }, + "binary-extensions": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.10.0.tgz", + "integrity": "sha1-muuabF6IY4qtFx4Wf1kAq+JINdA=", + "dev": true + }, + "boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=", + "dev": true + }, + "bowser": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-1.7.2.tgz", + "integrity": "sha1-uUzGklumteB8QhpY5gHORhEmRXI=" + }, + "brace-expansion": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz", + "integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI=", + "dev": true, + "requires": { + "balanced-match": "1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", + "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", + "dev": true, + "requires": { + "expand-range": "1.8.2", + "preserve": "0.2.0", + "repeat-element": "1.1.2" + } + }, + "browser-stdout": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.0.tgz", + "integrity": "sha1-81HTKWnTL6XXpVZxVCY9korjvR8=", + "dev": true + }, + "browserify-zlib": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.1.4.tgz", + "integrity": "sha1-uzX4pRn2AOD6a4SFJByXnQFB+y0=", + "dev": true, + "requires": { + "pako": "0.2.9" + } + }, + "browserslist": { + "version": "1.7.7", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-1.7.7.tgz", + "integrity": "sha1-C9dnBCWL6CmyOYu1Dkti0aFmsLk=", + "dev": true, + "requires": { + "caniuse-db": "1.0.30000718", + "electron-to-chromium": "1.3.18" + } + }, + "buffer": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", + "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=", + "dev": true, + "requires": { + "base64-js": "1.2.1", + "ieee754": "1.1.8", + "isarray": "1.0.0" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + } + } + }, + "bufferjs": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/bufferjs/-/bufferjs-3.0.1.tgz", + "integrity": "sha1-BpLoKcsQoQVQ5kc5CwNesGw46O8=", + "dev": true + }, + "builtin-modules": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", + "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", + "dev": true + }, + "bytes": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-2.5.0.tgz", + "integrity": "sha1-TJQj6i0lLCcMQbK97+/5u2tiwGo=", + "dev": true + }, + "caller-path": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz", + "integrity": "sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8=", + "dev": true, + "requires": { + "callsites": "0.2.0" + } + }, + "callsites": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz", + "integrity": "sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo=", + "dev": true + }, + "caniuse-api": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-1.6.1.tgz", + "integrity": "sha1-tTTnxzTE+B7F++isoq0kNUuWLGw=", + "dev": true, + "requires": { + "browserslist": "1.7.7", + "caniuse-db": "1.0.30000718", + "lodash.memoize": "4.1.2", + "lodash.uniq": "4.5.0" + } + }, + "caniuse-db": { + "version": "1.0.30000718", + "resolved": "https://registry.npmjs.org/caniuse-db/-/caniuse-db-1.0.30000718.tgz", + "integrity": "sha1-hs3ZeYcwJVSTTGHhBvTkcPFvmTw=", + "dev": true + }, + "center-align": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz", + "integrity": "sha1-qg0yYptu6XIgBBHL1EYckHvCt60=", + "dev": true, + "requires": { + "align-text": "0.1.4", + "lazy-cache": "1.0.4" + } + }, + "chain-function": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/chain-function/-/chain-function-1.0.0.tgz", + "integrity": "sha1-DUqzfn4Y6tC9xHuSB2QRjOWHM9w=" + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "2.2.1", + "escape-string-regexp": "1.0.5", + "has-ansi": "2.0.0", + "strip-ansi": "3.0.1", + "supports-color": "2.0.0" + } + }, + "change-emitter": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/change-emitter/-/change-emitter-0.1.6.tgz", + "integrity": "sha1-6LL+PX8at9aaMhma/5HqaTFAlRU=" + }, + "cheerio": { + "version": "0.22.0", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-0.22.0.tgz", + "integrity": "sha1-qbqoYKP5tZWmuBsahocxIe06Jp4=", + "dev": true, + "requires": { + "css-select": "1.2.0", + "dom-serializer": "0.1.0", + "entities": "1.1.1", + "htmlparser2": "3.9.2", + "lodash.assignin": "4.2.0", + "lodash.bind": "4.2.1", + "lodash.defaults": "4.2.0", + "lodash.filter": "4.6.0", + "lodash.flatten": "4.4.0", + "lodash.foreach": "4.5.0", + "lodash.map": "4.6.0", + "lodash.merge": "4.6.0", + "lodash.pick": "4.4.0", + "lodash.reduce": "4.6.0", + "lodash.reject": "4.6.0", + "lodash.some": "4.6.0" + } + }, + "chokidar": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-1.7.0.tgz", + "integrity": "sha1-eY5ol3gVHIB2tLNg5e3SjNortGg=", + "dev": true, + "requires": { + "anymatch": "1.3.2", + "async-each": "1.0.1", + "glob-parent": "2.0.0", + "inherits": "2.0.3", + "is-binary-path": "1.0.1", + "is-glob": "2.0.1", + "path-is-absolute": "1.0.1", + "readdirp": "2.1.0" + } + }, + "chromedriver": { + "version": "2.26.2", + "resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-2.26.2.tgz", + "integrity": "sha1-ugaegI55njqnnL9HIGpPClNpnfc=", + "dev": true, + "requires": { + "adm-zip": "0.4.7", + "kew": "0.5.0", + "mkdirp": "0.5.1", + "npmconf": "2.1.2", + "rimraf": "2.6.1" + } + }, + "circular-json": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.3.tgz", + "integrity": "sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A==", + "dev": true + }, + "clap": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/clap/-/clap-1.2.0.tgz", + "integrity": "sha1-WckP4+E3EEdG/xlGmiemNP9oyFc=", + "dev": true, + "requires": { + "chalk": "1.1.3" + } + }, + "cli-cursor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-1.0.2.tgz", + "integrity": "sha1-ZNo/fValRBLll5S9Ytw1KV6PKYc=", + "dev": true, + "requires": { + "restore-cursor": "1.0.1" + } + }, + "cli-width": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", + "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=", + "dev": true + }, + "clone": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.2.tgz", + "integrity": "sha1-Jgt6meux7f4kdTgXX3gyQ8sZ0Uk=", + "dev": true + }, + "co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", + "dev": true + }, + "coa": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/coa/-/coa-1.0.4.tgz", + "integrity": "sha1-qe8VNmDWqGqL3sAomlxoTSF0Mv0=", + "dev": true, + "requires": { + "q": "1.5.0" + } + }, + "code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", + "dev": true + }, + "color": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/color/-/color-0.11.4.tgz", + "integrity": "sha1-bXtcdPtl6EHNSHkq0e1eB7kE12Q=", + "dev": true, + "requires": { + "clone": "1.0.2", + "color-convert": "1.9.0", + "color-string": "0.3.0" + } + }, + "color-convert": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.0.tgz", + "integrity": "sha1-Gsz5fdc5uYO/mU1W/sj5WFNkG3o=", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "color-string": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-0.3.0.tgz", + "integrity": "sha1-J9RvtnAlxcL6JZk7+/V55HhBuZE=", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "colormin": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/colormin/-/colormin-1.1.2.tgz", + "integrity": "sha1-6i90IKcrlogaOKrlnsEkpvcpgTM=", + "dev": true, + "requires": { + "color": "0.11.4", + "css-color-names": "0.0.4", + "has": "1.0.1" + } + }, + "colors": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz", + "integrity": "sha1-FopHAXVran9RoSzgyXv6KMCE7WM=", + "dev": true + }, + "commander": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.11.0.tgz", + "integrity": "sha512-b0553uYA5YAEGgyYIGYROzKQ7X5RAqedkfjiZxwi0kL1g3bOaBNNZfYkzt/CL0umgD5wc9Jec2FbB98CjkMRvQ==", + "dev": true + }, + "commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", + "dev": true + }, + "compressible": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.11.tgz", + "integrity": "sha1-FnGKdd4oPtjmBAQWJaIGRYZ5fYo=", + "dev": true, + "requires": { + "mime-db": "1.29.0" + } + }, + "compression": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.0.tgz", + "integrity": "sha1-AwyfGY8WQ6BX13anOOki2kNzAS0=", + "dev": true, + "requires": { + "accepts": "1.3.3", + "bytes": "2.5.0", + "compressible": "2.0.11", + "debug": "2.6.8", + "on-headers": "1.0.1", + "safe-buffer": "5.1.1", + "vary": "1.1.1" + } + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "concat-stream": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.0.tgz", + "integrity": "sha1-CqxmL9Ur54lk1VMvaUeE5wEQrPc=", + "dev": true, + "requires": { + "inherits": "2.0.3", + "readable-stream": "2.3.3", + "typedarray": "0.0.6" + } + }, + "config-chain": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.11.tgz", + "integrity": "sha1-q6CXR9++TD5w52am5BWG4YWfxvI=", + "dev": true, + "requires": { + "ini": "1.3.4", + "proto-list": "1.2.4" + } + }, + "connect-history-api-fallback": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-1.3.0.tgz", + "integrity": "sha1-5R0X+PDvDbkKZP20feMFFVbp8Wk=", + "dev": true + }, + "console-browserify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.1.0.tgz", + "integrity": "sha1-8CQcRXMKn8YyOyBtvzjtx0HQuxA=", + "dev": true, + "requires": { + "date-now": "0.1.4" + } + }, + "constants-browserify": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-0.0.1.tgz", + "integrity": "sha1-kld9tSe6bEzwpFaNhLwDH0QeIfI=", + "dev": true + }, + "contains-path": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/contains-path/-/contains-path-0.1.0.tgz", + "integrity": "sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo=", + "dev": true + }, + "content-disposition": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", + "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=", + "dev": true + }, + "content-type": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.2.tgz", + "integrity": "sha1-t9ETrueo3Se9IRM8TcJSnfFyHu0=", + "dev": true + }, + "convert-source-map": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.5.0.tgz", + "integrity": "sha1-ms1whRxtXf3ZPZKC5e35SgP/RrU=", + "dev": true + }, + "cookie": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", + "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=", + "dev": true + }, + "cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=", + "dev": true + }, + "core-js": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.0.tgz", + "integrity": "sha1-VpwFCRi+ZIazg3VSAorgRmtxcIY=" + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true + }, + "crypto-browserify": { + "version": "3.2.8", + "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.2.8.tgz", + "integrity": "sha1-ubEdvm2WUd2IKgHmzEZ99xjs8Yk=", + "dev": true, + "requires": { + "pbkdf2-compat": "2.0.1", + "ripemd160": "0.2.0", + "sha.js": "2.2.6" + } + }, + "css-color-names": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/css-color-names/-/css-color-names-0.0.4.tgz", + "integrity": "sha1-gIrcLnnPhHOAabZGyyDsJ762KeA=", + "dev": true + }, + "css-in-js-utils": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/css-in-js-utils/-/css-in-js-utils-1.0.3.tgz", + "integrity": "sha1-msfgL3Y8+F2UAXZmVl7WiltfMhU=", + "requires": { + "hyphenate-style-name": "1.0.2" + } + }, + "css-loader": { + "version": "0.28.5", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-0.28.5.tgz", + "integrity": "sha512-/FJmsDD8e6xZOBHMFShN/BCjnrEybq0spYaTZ1QoZ10/jhUa1LDDojQELu/JJ1ykZZjt0nSwkYrb2Mfx3bZx3Q==", + "dev": true, + "requires": { + "babel-code-frame": "6.26.0", + "css-selector-tokenizer": "0.7.0", + "cssnano": "3.10.0", + "icss-utils": "2.1.0", + "loader-utils": "1.1.0", + "lodash.camelcase": "4.3.0", + "object-assign": "4.1.1", + "postcss": "5.2.17", + "postcss-modules-extract-imports": "1.1.0", + "postcss-modules-local-by-default": "1.2.0", + "postcss-modules-scope": "1.1.0", + "postcss-modules-values": "1.3.0", + "postcss-value-parser": "3.3.0", + "source-list-map": "2.0.0" + }, + "dependencies": { + "loader-utils": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.1.0.tgz", + "integrity": "sha1-yYrvSIvM7aL/teLeZG1qdUQp9c0=", + "dev": true, + "requires": { + "big.js": "3.1.3", + "emojis-list": "2.1.0", + "json5": "0.5.1" + } + } + } + }, + "css-select": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz", + "integrity": "sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=", + "dev": true, + "requires": { + "boolbase": "1.0.0", + "css-what": "2.1.0", + "domutils": "1.5.1", + "nth-check": "1.0.1" + } + }, + "css-selector-tokenizer": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/css-selector-tokenizer/-/css-selector-tokenizer-0.7.0.tgz", + "integrity": "sha1-5piEdK6MlTR3v15+/s/OzNnPTIY=", + "dev": true, + "requires": { + "cssesc": "0.1.0", + "fastparse": "1.1.1", + "regexpu-core": "1.0.0" + }, + "dependencies": { + "regexpu-core": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-1.0.0.tgz", + "integrity": "sha1-hqdj9Y7k18L2sQLkdkBQ3n7ZDGs=", + "dev": true, + "requires": { + "regenerate": "1.3.2", + "regjsgen": "0.2.0", + "regjsparser": "0.1.5" + } + } + } + }, + "css-what": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.0.tgz", + "integrity": "sha1-lGfQMsOM+u+58teVASUwYvh/ob0=", + "dev": true + }, + "cssesc": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-0.1.0.tgz", + "integrity": "sha1-yBSQPkViM3GgR3tAEJqq++6t27Q=", + "dev": true + }, + "cssnano": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-3.10.0.tgz", + "integrity": "sha1-Tzj2zqK5sX+gFJDyPx3GjqZcHDg=", + "dev": true, + "requires": { + "autoprefixer": "6.7.7", + "decamelize": "1.2.0", + "defined": "1.0.0", + "has": "1.0.1", + "object-assign": "4.1.1", + "postcss": "5.2.17", + "postcss-calc": "5.3.1", + "postcss-colormin": "2.2.2", + "postcss-convert-values": "2.6.1", + "postcss-discard-comments": "2.0.4", + "postcss-discard-duplicates": "2.1.0", + "postcss-discard-empty": "2.1.0", + "postcss-discard-overridden": "0.1.1", + "postcss-discard-unused": "2.2.3", + "postcss-filter-plugins": "2.0.2", + "postcss-merge-idents": "2.1.7", + "postcss-merge-longhand": "2.0.2", + "postcss-merge-rules": "2.1.2", + "postcss-minify-font-values": "1.0.5", + "postcss-minify-gradients": "1.0.5", + "postcss-minify-params": "1.2.2", + "postcss-minify-selectors": "2.1.1", + "postcss-normalize-charset": "1.1.1", + "postcss-normalize-url": "3.0.8", + "postcss-ordered-values": "2.2.3", + "postcss-reduce-idents": "2.4.0", + "postcss-reduce-initial": "1.0.1", + "postcss-reduce-transforms": "1.0.4", + "postcss-svgo": "2.1.6", + "postcss-unique-selectors": "2.0.2", + "postcss-value-parser": "3.3.0", + "postcss-zindex": "2.2.0" + } + }, + "csso": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/csso/-/csso-2.3.2.tgz", + "integrity": "sha1-3dUsWHAz9J6Utx/FVWnyUuj/X4U=", + "dev": true, + "requires": { + "clap": "1.2.0", + "source-map": "0.5.7" + } + }, + "d": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.0.tgz", + "integrity": "sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8=", + "dev": true, + "requires": { + "es5-ext": "0.10.29" + } + }, + "damerau-levenshtein": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.4.tgz", + "integrity": "sha1-AxkcQyy27qFou3fzpV/9zLiXhRQ=", + "dev": true + }, + "date-now": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/date-now/-/date-now-0.1.4.tgz", + "integrity": "sha1-6vQ5/U1ISK105cx9vvIAZyueNFs=", + "dev": true + }, + "debug": { + "version": "2.6.8", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.8.tgz", + "integrity": "sha1-5zFTHKLt4n0YgiJCfaF4IdaP9Pw=", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true + }, + "deep-equal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz", + "integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=" + }, + "deep-is": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", + "dev": true + }, + "define-properties": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.2.tgz", + "integrity": "sha1-g6c/L+pWmJj7c3GTyPhzyvbUXJQ=", + "requires": { + "foreach": "2.0.5", + "object-keys": "1.0.11" + } + }, + "defined": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz", + "integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=", + "dev": true + }, + "del": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/del/-/del-2.2.2.tgz", + "integrity": "sha1-wSyYHQZ4RshLyvhiz/kw2Qf/0ag=", + "dev": true, + "requires": { + "globby": "5.0.0", + "is-path-cwd": "1.0.0", + "is-path-in-cwd": "1.0.0", + "object-assign": "4.1.1", + "pify": "2.3.0", + "pinkie-promise": "2.0.1", + "rimraf": "2.6.1" + } + }, + "depd": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz", + "integrity": "sha1-V4O04cRZ8G+lyif5kfPQbnoxA1k=", + "dev": true + }, + "destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=", + "dev": true + }, + "detect-indent": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-4.0.0.tgz", + "integrity": "sha1-920GQ1LN9Docts5hnE7jqUdd4gg=", + "dev": true, + "requires": { + "repeating": "2.0.1" + } + }, + "diff": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-1.4.0.tgz", + "integrity": "sha1-fyjS657nsVqX79ic5j3P2qPMur8=", + "dev": true + }, + "doctrine": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.0.0.tgz", + "integrity": "sha1-xz2NKQnSIpHhoAejlYBNqLZl/mM=", + "dev": true, + "requires": { + "esutils": "2.0.2", + "isarray": "1.0.0" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + } + } + }, + "dom-helpers": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-3.2.1.tgz", + "integrity": "sha1-MgPgf+0he9H0JLAZc1WC/Deyglo=" + }, + "dom-serializer": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.0.tgz", + "integrity": "sha1-BzxpdUbOB4DOI75KKOKT5AvDDII=", + "dev": true, + "requires": { + "domelementtype": "1.1.3", + "entities": "1.1.1" + }, + "dependencies": { + "domelementtype": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.1.3.tgz", + "integrity": "sha1-vSh3PiZCiBrsUVRJJCmcXNgiGFs=", + "dev": true + } + } + }, + "domain-browser": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.1.7.tgz", + "integrity": "sha1-hnqksJP6oF8d4IwG9NeyH9+GmLw=", + "dev": true + }, + "domelementtype": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.0.tgz", + "integrity": "sha1-sXrtguirWeUt2cGbF1bg/BhyBMI=", + "dev": true + }, + "domhandler": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.1.tgz", + "integrity": "sha1-iS5HAAqZvlW783dP/qBWHYh5wlk=", + "dev": true, + "requires": { + "domelementtype": "1.3.0" + } + }, + "domutils": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", + "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=", + "dev": true, + "requires": { + "dom-serializer": "0.1.0", + "domelementtype": "1.3.0" + } + }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=", + "dev": true + }, + "electron-to-chromium": { + "version": "1.3.18", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.18.tgz", + "integrity": "sha1-PcyZ2j5rZl9qu8ccKK1Ros1zGpw=", + "dev": true + }, + "emoji-regex": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-6.5.1.tgz", + "integrity": "sha512-PAHp6TxrCy7MGMFidro8uikr+zlJJKJ/Q6mm2ExZ7HwkyR9lSVFfE3kt36qcwa24BQL7y0G9axycGjK1A/0uNQ==", + "dev": true + }, + "emojis-list": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz", + "integrity": "sha1-TapNnbAPmBmIDHn6RXrlsJof04k=", + "dev": true + }, + "encodeurl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.1.tgz", + "integrity": "sha1-eePVhlU0aQn+bw9Fpd5oEDspTSA=", + "dev": true + }, + "encoding": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz", + "integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=", + "requires": { + "iconv-lite": "0.4.18" + } + }, + "enhanced-resolve": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-0.9.1.tgz", + "integrity": "sha1-TW5omzcl+GCQknzMhs2fFjW4ni4=", + "dev": true, + "requires": { + "graceful-fs": "4.1.11", + "memory-fs": "0.2.0", + "tapable": "0.1.10" + }, + "dependencies": { + "memory-fs": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.2.0.tgz", + "integrity": "sha1-8rslNovBIeORwlIN6Slpyu4KApA=", + "dev": true + } + } + }, + "entities": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.1.tgz", + "integrity": "sha1-blwtClYhtdra7O+AuQ7ftc13cvA=", + "dev": true + }, + "enzyme": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/enzyme/-/enzyme-2.8.2.tgz", + "integrity": "sha1-bIvLBQEqvEqkvDIT+yN4C5tbFxQ=", + "dev": true, + "requires": { + "cheerio": "0.22.0", + "function.prototype.name": "1.0.3", + "is-subset": "0.1.1", + "lodash": "4.17.4", + "object-is": "1.0.1", + "object.assign": "4.0.4", + "object.entries": "1.0.4", + "object.values": "1.0.4", + "prop-types": "15.5.10", + "uuid": "2.0.3" + } + }, + "errno": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.4.tgz", + "integrity": "sha1-uJbiOp5ei6M4cfyZar02NfyaHH0=", + "dev": true, + "requires": { + "prr": "0.0.0" + } + }, + "es-abstract": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.8.0.tgz", + "integrity": "sha512-Cf9/h5MrXtExM20gSS55YFrGKCyPrRBjIVBtVyy8vmlsDfe0NPKMWj65tPLgzyfPuapWxh5whpXCtW4+AW5mRg==", + "requires": { + "es-to-primitive": "1.1.1", + "function-bind": "1.1.0", + "has": "1.0.1", + "is-callable": "1.1.3", + "is-regex": "1.0.4" + } + }, + "es-to-primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.1.1.tgz", + "integrity": "sha1-RTVSSKiJeQNLZ5Lhm7gfK3l13Q0=", + "requires": { + "is-callable": "1.1.3", + "is-date-object": "1.0.1", + "is-symbol": "1.0.1" + } + }, + "es5-ext": { + "version": "0.10.29", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.29.tgz", + "integrity": "sha512-KXla9NXo5sdaEkGSmbFPYgjH6m75kxsthL6GDRSug/Y2OiMoYm0I9giL39j4cgmaFmAbkIFJ6gG+SGKnLSmOvA==", + "dev": true, + "requires": { + "es6-iterator": "2.0.1", + "es6-symbol": "3.1.1" + } + }, + "es6-error": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.0.2.tgz", + "integrity": "sha1-7sXHJurO9Rt/a3PCDbbhsTsGnJg=" + }, + "es6-iterator": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.1.tgz", + "integrity": "sha1-jjGcnwRTv1ddN0lAplWSDlnKVRI=", + "dev": true, + "requires": { + "d": "1.0.0", + "es5-ext": "0.10.29", + "es6-symbol": "3.1.1" + } + }, + "es6-map": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/es6-map/-/es6-map-0.1.5.tgz", + "integrity": "sha1-kTbgUD3MBqMBaQ8LsU/042TpSfA=", + "dev": true, + "requires": { + "d": "1.0.0", + "es5-ext": "0.10.29", + "es6-iterator": "2.0.1", + "es6-set": "0.1.5", + "es6-symbol": "3.1.1", + "event-emitter": "0.3.5" + } + }, + "es6-set": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/es6-set/-/es6-set-0.1.5.tgz", + "integrity": "sha1-0rPsXU2ADO2BjbU40ol02wpzzLE=", + "dev": true, + "requires": { + "d": "1.0.0", + "es5-ext": "0.10.29", + "es6-iterator": "2.0.1", + "es6-symbol": "3.1.1", + "event-emitter": "0.3.5" + } + }, + "es6-symbol": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.1.tgz", + "integrity": "sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc=", + "dev": true, + "requires": { + "d": "1.0.0", + "es5-ext": "0.10.29" + } + }, + "es6-weak-map": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.2.tgz", + "integrity": "sha1-XjqzIlH/0VOKH45f+hNXdy+S2W8=", + "dev": true, + "requires": { + "d": "1.0.0", + "es5-ext": "0.10.29", + "es6-iterator": "2.0.1", + "es6-symbol": "3.1.1" + } + }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "escope": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/escope/-/escope-3.6.0.tgz", + "integrity": "sha1-4Bl16BJ4GhY6ba392AOY3GTIicM=", + "dev": true, + "requires": { + "es6-map": "0.1.5", + "es6-weak-map": "2.0.2", + "esrecurse": "4.2.0", + "estraverse": "4.2.0" + } + }, + "eslint": { + "version": "3.19.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-3.19.0.tgz", + "integrity": "sha1-yPxiAcf0DdCJQbh8CFdnOGpnmsw=", + "dev": true, + "requires": { + "babel-code-frame": "6.26.0", + "chalk": "1.1.3", + "concat-stream": "1.6.0", + "debug": "2.6.8", + "doctrine": "2.0.0", + "escope": "3.6.0", + "espree": "3.5.0", + "esquery": "1.0.0", + "estraverse": "4.2.0", + "esutils": "2.0.2", + "file-entry-cache": "2.0.0", + "glob": "7.1.2", + "globals": "9.18.0", + "ignore": "3.3.3", + "imurmurhash": "0.1.4", + "inquirer": "0.12.0", + "is-my-json-valid": "2.16.1", + "is-resolvable": "1.0.0", + "js-yaml": "3.7.0", + "json-stable-stringify": "1.0.1", + "levn": "0.3.0", + "lodash": "4.17.4", + "mkdirp": "0.5.1", + "natural-compare": "1.4.0", + "optionator": "0.8.2", + "path-is-inside": "1.0.2", + "pluralize": "1.2.1", + "progress": "1.1.8", + "require-uncached": "1.0.3", + "shelljs": "0.7.8", + "strip-bom": "3.0.0", + "strip-json-comments": "2.0.1", + "table": "3.8.3", + "text-table": "0.2.0", + "user-home": "2.0.0" + }, + "dependencies": { + "user-home": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/user-home/-/user-home-2.0.0.tgz", + "integrity": "sha1-nHC/2Babwdy/SGBODwS4tJzenp8=", + "dev": true, + "requires": { + "os-homedir": "1.0.2" + } + } + } + }, + "eslint-config-prettier": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-2.4.0.tgz", + "integrity": "sha1-+3zynAqyumGvUWT7GTD5vvO+KHI=", + "dev": true, + "requires": { + "get-stdin": "5.0.1" + } + }, + "eslint-import-resolver-node": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.2.3.tgz", + "integrity": "sha1-Wt2BBujJKNssuiMrzZ76hG49oWw=", + "dev": true, + "requires": { + "debug": "2.6.8", + "object-assign": "4.1.1", + "resolve": "1.4.0" + } + }, + "eslint-module-utils": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.1.1.tgz", + "integrity": "sha512-jDI/X5l/6D1rRD/3T43q8Qgbls2nq5km5KSqiwlyUbGo5+04fXhMKdCPhjwbqAa6HXWaMxj8Q4hQDIh7IadJQw==", + "dev": true, + "requires": { + "debug": "2.6.8", + "pkg-dir": "1.0.0" + } + }, + "eslint-plugin-import": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.2.0.tgz", + "integrity": "sha1-crowb60wXWfEgWNIpGmaQimsi04=", + "dev": true, + "requires": { + "builtin-modules": "1.1.1", + "contains-path": "0.1.0", + "debug": "2.6.8", + "doctrine": "1.5.0", + "eslint-import-resolver-node": "0.2.3", + "eslint-module-utils": "2.1.1", + "has": "1.0.1", + "lodash.cond": "4.5.2", + "minimatch": "3.0.4", + "pkg-up": "1.0.0" + }, + "dependencies": { + "doctrine": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz", + "integrity": "sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=", + "dev": true, + "requires": { + "esutils": "2.0.2", + "isarray": "1.0.0" + } + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + } + } + }, + "eslint-plugin-jsx-a11y": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-4.0.0.tgz", + "integrity": "sha1-d5uw/nsI2lZKQiYkkR3hAGHgSO4=", + "dev": true, + "requires": { + "aria-query": "0.3.0", + "ast-types-flow": "0.0.7", + "damerau-levenshtein": "1.0.4", + "emoji-regex": "6.5.1", + "jsx-ast-utils": "1.4.1", + "object-assign": "4.1.1" + } + }, + "eslint-plugin-prettier": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-2.2.0.tgz", + "integrity": "sha512-LmIOP99A+BMeMYZoDeU196cV7FNIHJEjjWLARdz6JSTYmGK+HTEAVbJukPr/ZVOKPs5Hf7b10aXwSbty6Gqqsw==", + "dev": true, + "requires": { + "fast-diff": "1.1.1", + "jest-docblock": "20.0.3" + } + }, + "eslint-plugin-react": { + "version": "6.10.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-6.10.3.tgz", + "integrity": "sha1-xUNb6wZ3ThLH2y9qut3L+QDNP3g=", + "dev": true, + "requires": { + "array.prototype.find": "2.0.4", + "doctrine": "1.5.0", + "has": "1.0.1", + "jsx-ast-utils": "1.4.1", + "object.assign": "4.0.4" + }, + "dependencies": { + "doctrine": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz", + "integrity": "sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=", + "dev": true, + "requires": { + "esutils": "2.0.2", + "isarray": "1.0.0" + } + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + } + } + }, + "espree": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-3.5.0.tgz", + "integrity": "sha1-mDWGJb3QVYYeon4oZ+pyn69GPY0=", + "dev": true, + "requires": { + "acorn": "5.1.1", + "acorn-jsx": "3.0.1" + } + }, + "esprima": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", + "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=", + "dev": true + }, + "esquery": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.0.tgz", + "integrity": "sha1-z7qLV9f7qT8XKYqKAGoEzaE9gPo=", + "dev": true, + "requires": { + "estraverse": "4.2.0" + } + }, + "esrecurse": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.0.tgz", + "integrity": "sha1-+pVo2Y04I/mkHZHpAtyrnqblsWM=", + "dev": true, + "requires": { + "estraverse": "4.2.0", + "object-assign": "4.1.1" + } + }, + "estraverse": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", + "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=", + "dev": true + }, + "esutils": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", + "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", + "dev": true + }, + "etag": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.0.tgz", + "integrity": "sha1-b2Ma7zNtbEY2K1F2QETOIWvjwFE=", + "dev": true + }, + "event-emitter": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", + "integrity": "sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk=", + "dev": true, + "requires": { + "d": "1.0.0", + "es5-ext": "0.10.29" + } + }, + "eventemitter3": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-1.2.0.tgz", + "integrity": "sha1-HIaZHYFq0eUEdQ5zh0Ik7PO+xQg=", + "dev": true + }, + "events": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", + "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=", + "dev": true + }, + "eventsource": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-0.1.6.tgz", + "integrity": "sha1-Cs7ehJ7X3RzMMsgRuxG5RNTykjI=", + "dev": true, + "requires": { + "original": "1.0.0" + } + }, + "exit-hook": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/exit-hook/-/exit-hook-1.1.1.tgz", + "integrity": "sha1-8FyiM7SMBdVP/wd2XfhQfpXAL/g=", + "dev": true + }, + "expand-brackets": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", + "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", + "dev": true, + "requires": { + "is-posix-bracket": "0.1.1" + } + }, + "expand-range": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-1.8.2.tgz", + "integrity": "sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc=", + "dev": true, + "requires": { + "fill-range": "2.2.3" + } + }, + "express": { + "version": "4.15.4", + "resolved": "https://registry.npmjs.org/express/-/express-4.15.4.tgz", + "integrity": "sha1-Ay4iU0ic+PzgJma+yj0R7XotrtE=", + "dev": true, + "requires": { + "accepts": "1.3.3", + "array-flatten": "1.1.1", + "content-disposition": "0.5.2", + "content-type": "1.0.2", + "cookie": "0.3.1", + "cookie-signature": "1.0.6", + "debug": "2.6.8", + "depd": "1.1.1", + "encodeurl": "1.0.1", + "escape-html": "1.0.3", + "etag": "1.8.0", + "finalhandler": "1.0.4", + "fresh": "0.5.0", + "merge-descriptors": "1.0.1", + "methods": "1.1.2", + "on-finished": "2.3.0", + "parseurl": "1.3.1", + "path-to-regexp": "0.1.7", + "proxy-addr": "1.1.5", + "qs": "6.5.0", + "range-parser": "1.2.0", + "send": "0.15.4", + "serve-static": "1.12.4", + "setprototypeof": "1.0.3", + "statuses": "1.3.1", + "type-is": "1.6.15", + "utils-merge": "1.0.0", + "vary": "1.1.1" + }, + "dependencies": { + "path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=", + "dev": true + } + } + }, + "extglob": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", + "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", + "dev": true, + "requires": { + "is-extglob": "1.0.0" + } + }, + "extract-text-webpack-plugin": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/extract-text-webpack-plugin/-/extract-text-webpack-plugin-1.0.1.tgz", + "integrity": "sha1-yVvzy6rEnclvHcbgclSfu2VMzSw=", + "dev": true, + "requires": { + "async": "1.5.2", + "loader-utils": "0.2.17", + "webpack-sources": "0.1.5" + } + }, + "fast-diff": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.1.1.tgz", + "integrity": "sha1-CuoOTmBbaiGJ8Ok21Lf7rxt8/Zs=", + "dev": true + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "dev": true + }, + "fastparse": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.1.tgz", + "integrity": "sha1-0eJkOzipTXWDtHkGDmxK/8lAcfg=", + "dev": true + }, + "faye-websocket": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.10.0.tgz", + "integrity": "sha1-TkkvjQTftviQA1B/btvy1QHnxvQ=", + "dev": true, + "requires": { + "websocket-driver": "0.6.5" + } + }, + "fbjs": { + "version": "0.8.14", + "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-0.8.14.tgz", + "integrity": "sha1-0dviviVMNakeCfMfnNUKQLKg7Rw=", + "requires": { + "core-js": "1.2.7", + "isomorphic-fetch": "2.2.1", + "loose-envify": "1.3.1", + "object-assign": "4.1.1", + "promise": "7.3.1", + "setimmediate": "1.0.5", + "ua-parser-js": "0.7.14" + }, + "dependencies": { + "core-js": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz", + "integrity": "sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY=" + } + } + }, + "figures": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", + "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=", + "dev": true, + "requires": { + "escape-string-regexp": "1.0.5", + "object-assign": "4.1.1" + } + }, + "file-api": { + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/file-api/-/file-api-0.10.4.tgz", + "integrity": "sha1-LxASJttyfMAXKg3WiPL2iD1SiD0=", + "dev": true, + "requires": { + "File": "0.10.2", + "FileList": "0.10.2", + "bufferjs": "3.0.1", + "file-error": "0.10.2", + "filereader": "0.10.3", + "formdata": "0.10.4", + "mime": "1.3.4", + "remedial": "1.0.7" + } + }, + "file-entry-cache": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-2.0.0.tgz", + "integrity": "sha1-w5KZDD5oR4PYOLjISkXYoEhFg2E=", + "dev": true, + "requires": { + "flat-cache": "1.2.2", + "object-assign": "4.1.1" + } + }, + "file-error": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/file-error/-/file-error-0.10.2.tgz", + "integrity": "sha1-ljtIuSc7PUuEtADuVxvHixc5cko=", + "dev": true + }, + "filename-regex": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz", + "integrity": "sha1-wcS5vuPglyXdsQa3XB4wH+LxiyY=", + "dev": true + }, + "filereader": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/filereader/-/filereader-0.10.3.tgz", + "integrity": "sha1-x0fUos2PYeVBinwH/hJXpD8KzbE=", + "dev": true + }, + "fill-range": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.3.tgz", + "integrity": "sha1-ULd9/X5Gm8dJJHCWNpn+eoSFpyM=", + "dev": true, + "requires": { + "is-number": "2.1.0", + "isobject": "2.1.0", + "randomatic": "1.1.7", + "repeat-element": "1.1.2", + "repeat-string": "1.6.1" + } + }, + "finalhandler": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.0.4.tgz", + "integrity": "sha512-16l/r8RgzlXKmFOhZpHBztvye+lAhC5SU7hXavnerC9UfZqZxxXl3BzL8MhffPT3kF61lj9Oav2LKEzh0ei7tg==", + "dev": true, + "requires": { + "debug": "2.6.8", + "encodeurl": "1.0.1", + "escape-html": "1.0.3", + "on-finished": "2.3.0", + "parseurl": "1.3.1", + "statuses": "1.3.1", + "unpipe": "1.0.0" + } + }, + "find-cache-dir": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-0.1.1.tgz", + "integrity": "sha1-yN765XyKUqinhPnjHFfHQumToLk=", + "dev": true, + "requires": { + "commondir": "1.0.1", + "mkdirp": "0.5.1", + "pkg-dir": "1.0.0" + } + }, + "find-up": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", + "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", + "dev": true, + "requires": { + "path-exists": "2.1.0", + "pinkie-promise": "2.0.1" + } + }, + "flat-cache": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.2.2.tgz", + "integrity": "sha1-+oZxTnLCHbiGAXYezy9VXRq8a5Y=", + "dev": true, + "requires": { + "circular-json": "0.3.3", + "del": "2.2.2", + "graceful-fs": "4.1.11", + "write": "0.2.1" + } + }, + "flatten": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/flatten/-/flatten-1.0.2.tgz", + "integrity": "sha1-2uRqnXj74lKSJYzB54CkHZXAN4I=", + "dev": true + }, + "for-each": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.2.tgz", + "integrity": "sha1-LEBFC5NI6X8oEyJZO6lnBLmr1NQ=", + "requires": { + "is-function": "1.0.1" + } + }, + "for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", + "dev": true + }, + "for-own": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz", + "integrity": "sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4=", + "dev": true, + "requires": { + "for-in": "1.0.2" + } + }, + "foreach": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz", + "integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k=" + }, + "foreachasync": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/foreachasync/-/foreachasync-3.0.0.tgz", + "integrity": "sha1-VQKYfchxS+M5IJfzLgBxyd7gfPY=", + "dev": true + }, + "formatio": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/formatio/-/formatio-1.2.0.tgz", + "integrity": "sha1-87IWfZBoxGmKjVH092CjmlTYGOs=", + "dev": true, + "requires": { + "samsam": "1.2.1" + } + }, + "formdata": { + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/formdata/-/formdata-0.10.4.tgz", + "integrity": "sha1-liH9wMw2H0oBEd5dJbNfanjcVaA=", + "dev": true, + "requires": { + "File": "0.10.2", + "FileList": "0.10.2", + "bufferjs": "2.0.0", + "filereader": "0.10.3", + "foreachasync": "3.0.0", + "remedial": "1.0.7" + }, + "dependencies": { + "bufferjs": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/bufferjs/-/bufferjs-2.0.0.tgz", + "integrity": "sha1-aF5x7VwGAOPXA/+b0BK7MnCjnig=", + "dev": true + } + } + }, + "forwarded": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.0.tgz", + "integrity": "sha1-Ge+YdMSuHCl7zweP3mOgm2aoQ2M=", + "dev": true + }, + "fresh": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.0.tgz", + "integrity": "sha1-9HTKXmqSRtb9jglTz6m5yAWvp44=", + "dev": true + }, + "fs-readdir-recursive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-readdir-recursive/-/fs-readdir-recursive-1.0.0.tgz", + "integrity": "sha1-jNF0XItPiinIyuw5JHaSG6GV9WA=", + "dev": true + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "full-icu": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/full-icu/-/full-icu-1.1.3.tgz", + "integrity": "sha1-p1X4GgBaPQl0D3jnwjERzDRpYhk=", + "dev": true, + "requires": { + "icu4c-data": "0.59.2" + }, + "dependencies": { + "icu4c-data": { + "version": "0.59.2", + "resolved": "https://registry.npmjs.org/icu4c-data/-/icu4c-data-0.59.2.tgz", + "integrity": "sha1-Xf97e+4H/fp6ybtgHMe4gEaha+Y=", + "dev": true + } + } + }, + "function-bind": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.0.tgz", + "integrity": "sha1-FhdnFMgBeY5Ojyz391KUZ7tKV3E=" + }, + "function.prototype.name": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.0.3.tgz", + "integrity": "sha512-5EblxZUdioXi2JiMZ9FUbwYj40eQ9MFHyzFLBSPdlRl3SO8l7SLWuAnQ/at/1Wi4hjJwME/C5WpF2ZfAc8nGNw==", + "dev": true, + "requires": { + "define-properties": "1.1.2", + "function-bind": "1.1.0", + "is-callable": "1.1.3" + } + }, + "generate-function": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.0.0.tgz", + "integrity": "sha1-aFj+fAlpt9TpCTM3ZHrHn2DfvnQ=", + "dev": true + }, + "generate-object-property": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz", + "integrity": "sha1-nA4cQDCM6AT0eDYYuTf6iPmdUNA=", + "dev": true, + "requires": { + "is-property": "1.0.2" + } + }, + "get-stdin": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-5.0.1.tgz", + "integrity": "sha1-Ei4WFZHiH/TFJTAwVpPyDmOTo5g=", + "dev": true + }, + "glob": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "dev": true, + "requires": { + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" + } + }, + "glob-base": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz", + "integrity": "sha1-27Fk9iIbHAscz4Kuoyi0l98Oo8Q=", + "dev": true, + "requires": { + "glob-parent": "2.0.0", + "is-glob": "2.0.1" + } + }, + "glob-parent": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz", + "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=", + "dev": true, + "requires": { + "is-glob": "2.0.1" + } + }, + "globals": { + "version": "9.18.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", + "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", + "dev": true + }, + "globby": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-5.0.0.tgz", + "integrity": "sha1-69hGZ8oNuzMLmbz8aOrCvFQ3Dg0=", + "dev": true, + "requires": { + "array-union": "1.0.2", + "arrify": "1.0.1", + "glob": "7.1.2", + "object-assign": "4.1.1", + "pify": "2.3.0", + "pinkie-promise": "2.0.1" + } + }, + "graceful-fs": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", + "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", + "dev": true + }, + "graceful-readlink": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz", + "integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=", + "dev": true + }, + "growl": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.9.2.tgz", + "integrity": "sha1-Dqd0NxXbjY3ixe3hd14bRayFwC8=", + "dev": true + }, + "has": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.1.tgz", + "integrity": "sha1-hGFzP1OLCDfJNh45qauelwTcLyg=", + "requires": { + "function-bind": "1.1.0" + } + }, + "has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "dev": true, + "requires": { + "ansi-regex": "2.1.1" + } + }, + "has-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "dev": true + }, + "history": { + "version": "4.6.3", + "resolved": "https://registry.npmjs.org/history/-/history-4.6.3.tgz", + "integrity": "sha1-bXI6hxLFgda+836MJvSu3G64aWc=", + "requires": { + "invariant": "2.2.2", + "loose-envify": "1.3.1", + "resolve-pathname": "2.1.0", + "value-equal": "0.2.1", + "warning": "3.0.0" + } + }, + "hoist-non-react-statics": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-1.2.0.tgz", + "integrity": "sha1-qkSM8JhtVcxAdzsXF0t90GbLfPs=" + }, + "home-or-tmp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/home-or-tmp/-/home-or-tmp-2.0.0.tgz", + "integrity": "sha1-42w/LSyufXRqhX440Y1fMqeILbg=", + "dev": true, + "requires": { + "os-homedir": "1.0.2", + "os-tmpdir": "1.0.2" + } + }, + "html-comment-regex": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/html-comment-regex/-/html-comment-regex-1.1.1.tgz", + "integrity": "sha1-ZouTd26q5V696POtRkswekljYl4=", + "dev": true + }, + "htmlparser2": { + "version": "3.9.2", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.9.2.tgz", + "integrity": "sha1-G9+HrMoPP55T+k/M6w9LTLsAszg=", + "dev": true, + "requires": { + "domelementtype": "1.3.0", + "domhandler": "2.4.1", + "domutils": "1.5.1", + "entities": "1.1.1", + "inherits": "2.0.3", + "readable-stream": "2.3.3" + } + }, + "http-browserify": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/http-browserify/-/http-browserify-1.7.0.tgz", + "integrity": "sha1-M3la3nLfiKz7/TZ3PO/tp2RzWyA=", + "dev": true, + "requires": { + "Base64": "0.2.1", + "inherits": "2.0.3" + } + }, + "http-errors": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.2.tgz", + "integrity": "sha1-CgAsyFcHGSp+eUbO7cERVfYOxzY=", + "dev": true, + "requires": { + "depd": "1.1.1", + "inherits": "2.0.3", + "setprototypeof": "1.0.3", + "statuses": "1.3.1" + } + }, + "http-proxy": { + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.16.2.tgz", + "integrity": "sha1-Bt/ykpUr9k2+hHH6nfcwZtTzd0I=", + "dev": true, + "requires": { + "eventemitter3": "1.2.0", + "requires-port": "1.0.0" + } + }, + "http-proxy-middleware": { + "version": "0.17.4", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-0.17.4.tgz", + "integrity": "sha1-ZC6ISIUdZvCdTxJJEoRtuutBuDM=", + "dev": true, + "requires": { + "http-proxy": "1.16.2", + "is-glob": "3.1.0", + "lodash": "4.17.4", + "micromatch": "2.3.11" + }, + "dependencies": { + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, + "is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "dev": true, + "requires": { + "is-extglob": "2.1.1" + } + } + } + }, + "https-browserify": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-0.0.0.tgz", + "integrity": "sha1-s//f5zSyo9Sp79WOhlTJH86G6v0=", + "dev": true + }, + "hyphenate-style-name": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.0.2.tgz", + "integrity": "sha1-MRYKNpMK2vH8BMYHT360FGXU7Es=" + }, + "iconv-lite": { + "version": "0.4.18", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.18.tgz", + "integrity": "sha512-sr1ZQph3UwHTR0XftSbK85OvBbxe/abLGzEnPENCQwmHf7sck8Oyu4ob3LgBxWWxRoM+QszeUyl7jbqapu2TqA==" + }, + "icss-replace-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/icss-replace-symbols/-/icss-replace-symbols-1.1.0.tgz", + "integrity": "sha1-Bupvg2ead0njhs/h/oEq5dsiPe0=", + "dev": true + }, + "icss-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-2.1.0.tgz", + "integrity": "sha1-g/Cg7DeL8yRheLbCrZE28TWxyWI=", + "dev": true, + "requires": { + "postcss": "6.0.9" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz", + "integrity": "sha512-NnSOmMEYtVR2JVMIGTzynRkkaxtiq1xnFBcdQD/DnNCYPoEPsVJhM98BDyaoNOQIi7p4okdi3E27eN7GQbsUug==", + "dev": true, + "requires": { + "color-convert": "1.9.0" + } + }, + "chalk": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.1.0.tgz", + "integrity": "sha512-LUHGS/dge4ujbXMJrnihYMcL4AoOweGnw9Tp3kQuqy1Kx5c1qKjqvMJZ6nVJPMWJtKCTN72ZogH3oeSO9g9rXQ==", + "dev": true, + "requires": { + "ansi-styles": "3.2.0", + "escape-string-regexp": "1.0.5", + "supports-color": "4.2.1" + } + }, + "has-flag": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", + "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=", + "dev": true + }, + "postcss": { + "version": "6.0.9", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.9.tgz", + "integrity": "sha512-bBE2AHNEBhF23TfET6AA/lFP8ah+qHOZoFJEflFG+HgvVLdTmMOrocx/4LVVDIn3w6jUssw1q2Exk1cc9UOI8w==", + "dev": true, + "requires": { + "chalk": "2.1.0", + "source-map": "0.5.7", + "supports-color": "4.2.1" + } + }, + "supports-color": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.2.1.tgz", + "integrity": "sha512-qxzYsob3yv6U+xMzPrv170y8AwGP7i74g+pbixCfD6rgso8BscLT2qXIuz6TpOaiJZ3mFgT5O9lyT9nMU4LfaA==", + "dev": true, + "requires": { + "has-flag": "2.0.0" + } + } + } + }, + "ieee754": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.8.tgz", + "integrity": "sha1-vjPUCsEO8ZJnAfbwii2G+/0a0+Q=", + "dev": true + }, + "ignore": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.3.tgz", + "integrity": "sha1-QyNS5XrM2HqzEQ6C0/6g5HgSFW0=", + "dev": true + }, + "ignore-styles": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ignore-styles/-/ignore-styles-5.0.1.tgz", + "integrity": "sha1-tJ7yJ0va/NikiAqWa/440aC/RnE=", + "dev": true + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true + }, + "indexes-of": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/indexes-of/-/indexes-of-1.0.1.tgz", + "integrity": "sha1-8w9xbI4r00bHtn0985FVZqfAVgc=", + "dev": true + }, + "indexof": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz", + "integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=", + "dev": true + }, + "inflection": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/inflection/-/inflection-1.12.0.tgz", + "integrity": "sha1-ogCTVlbW9fa8TcdQLhrstwMihBY=" + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "1.4.0", + "wrappy": "1.0.2" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + }, + "ini": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.4.tgz", + "integrity": "sha1-BTfLedr1m1mhpRff9wbIbsA5Fi4=", + "dev": true + }, + "inline-style-prefixer": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/inline-style-prefixer/-/inline-style-prefixer-3.0.7.tgz", + "integrity": "sha1-DMyS5ZAv5uDSjZdcQlhEP4gGFfg=", + "requires": { + "bowser": "1.7.2", + "css-in-js-utils": "1.0.3" + } + }, + "inquirer": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-0.12.0.tgz", + "integrity": "sha1-HvK/1jUE3wvHV4X/+MLEHfEvB34=", + "dev": true, + "requires": { + "ansi-escapes": "1.4.0", + "ansi-regex": "2.1.1", + "chalk": "1.1.3", + "cli-cursor": "1.0.2", + "cli-width": "2.2.0", + "figures": "1.7.0", + "lodash": "4.17.4", + "readline2": "1.0.1", + "run-async": "0.1.0", + "rx-lite": "3.1.2", + "string-width": "1.0.2", + "strip-ansi": "3.0.1", + "through": "2.3.8" + } + }, + "interpret": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.0.3.tgz", + "integrity": "sha1-y8NcYu7uc/Gat7EKgBURQBr8D5A=", + "dev": true + }, + "invariant": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.2.tgz", + "integrity": "sha1-nh9WrArNtr8wMwbzOL47IErmA2A=", + "requires": { + "loose-envify": "1.3.1" + } + }, + "ipaddr.js": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.4.0.tgz", + "integrity": "sha1-KWrKh4qCGBbluF0KKFqZvP9FgvA=", + "dev": true + }, + "is-absolute-url": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-2.1.0.tgz", + "integrity": "sha1-UFMN+4T8yap9vnhS6Do3uTufKqY=", + "dev": true + }, + "is-binary-path": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", + "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", + "dev": true, + "requires": { + "binary-extensions": "1.10.0" + } + }, + "is-buffer": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.5.tgz", + "integrity": "sha1-Hzsm72E7IUuIy8ojzGwB2Hlh7sw=", + "dev": true + }, + "is-callable": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.3.tgz", + "integrity": "sha1-hut1OSgF3cM69xySoO7fdO52BLI=" + }, + "is-date-object": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz", + "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=" + }, + "is-dotfile": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-dotfile/-/is-dotfile-1.0.3.tgz", + "integrity": "sha1-pqLzL/0t+wT1yiXs0Pa4PPeYoeE=", + "dev": true + }, + "is-equal-shallow": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz", + "integrity": "sha1-IjgJj8Ih3gvPpdnqxMRdY4qhxTQ=", + "dev": true, + "requires": { + "is-primitive": "2.0.0" + } + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", + "dev": true + }, + "is-extglob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", + "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", + "dev": true + }, + "is-finite": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz", + "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=", + "dev": true, + "requires": { + "number-is-nan": "1.0.1" + } + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, + "requires": { + "number-is-nan": "1.0.1" + } + }, + "is-function": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-function/-/is-function-1.0.1.tgz", + "integrity": "sha1-Es+5i2W1fdPRk6MSH19uL0N2ArU=" + }, + "is-glob": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", + "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", + "dev": true, + "requires": { + "is-extglob": "1.0.0" + } + }, + "is-my-json-valid": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.16.1.tgz", + "integrity": "sha512-ochPsqWS1WXj8ZnMIV0vnNXooaMhp7cyL4FMSIPKTtnV0Ha/T19G2b9kkhcNsabV9bxYkze7/aLZJb/bYuFduQ==", + "dev": true, + "requires": { + "generate-function": "2.0.0", + "generate-object-property": "1.2.0", + "jsonpointer": "4.0.1", + "xtend": "4.0.1" + } + }, + "is-number": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz", + "integrity": "sha1-Afy7s5NGOlSPL0ZszhbezknbkI8=", + "dev": true, + "requires": { + "kind-of": "3.2.2" + } + }, + "is-path-cwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz", + "integrity": "sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0=", + "dev": true + }, + "is-path-in-cwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.0.tgz", + "integrity": "sha1-ZHdYK4IU1gI0YJRWcAO+ip6sBNw=", + "dev": true, + "requires": { + "is-path-inside": "1.0.0" + } + }, + "is-path-inside": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.0.tgz", + "integrity": "sha1-/AbloWg/vaE95mev9xe7wQpI838=", + "dev": true, + "requires": { + "path-is-inside": "1.0.2" + } + }, + "is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=", + "dev": true + }, + "is-posix-bracket": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz", + "integrity": "sha1-MzTceXdDaOkvAW5vvAqI9c1ua8Q=", + "dev": true + }, + "is-primitive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-primitive/-/is-primitive-2.0.0.tgz", + "integrity": "sha1-IHurkWOEmcB7Kt8kCkGochADRXU=", + "dev": true + }, + "is-promise": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", + "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=" + }, + "is-property": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", + "integrity": "sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ=", + "dev": true + }, + "is-regex": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", + "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", + "requires": { + "has": "1.0.1" + } + }, + "is-resolvable": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.0.0.tgz", + "integrity": "sha1-jfV8YeouPFAUCNEA+wE8+NbgzGI=", + "dev": true, + "requires": { + "tryit": "1.0.3" + } + }, + "is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" + }, + "is-subset": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-subset/-/is-subset-0.1.1.tgz", + "integrity": "sha1-ilkRfZMt4d4A8kX83TnOQ/HpOaY=", + "dev": true + }, + "is-svg": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-svg/-/is-svg-2.1.0.tgz", + "integrity": "sha1-z2EJDaDZ77yrhyLeum8DIgjbsOk=", + "dev": true, + "requires": { + "html-comment-regex": "1.1.1" + } + }, + "is-symbol": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.1.tgz", + "integrity": "sha1-PMWfAAJRlLarLjjbrmaJJWtmBXI=" + }, + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" + }, + "isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "dev": true, + "requires": { + "isarray": "1.0.0" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + } + } + }, + "isomorphic-fetch": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz", + "integrity": "sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk=", + "requires": { + "node-fetch": "1.7.2", + "whatwg-fetch": "2.0.3" + } + }, + "jest-docblock": { + "version": "20.0.3", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-20.0.3.tgz", + "integrity": "sha1-F76phDQswz2DxQ++FUXqDvqkRxI=", + "dev": true + }, + "js-base64": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.1.9.tgz", + "integrity": "sha1-8OgK4DmkvWVLXygfyT8EqRSn/M4=", + "dev": true + }, + "js-tokens": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", + "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=" + }, + "js-yaml": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.7.0.tgz", + "integrity": "sha1-XJZ93YN6m/3KXy3oQlOr6KHAO4A=", + "dev": true, + "requires": { + "argparse": "1.0.9", + "esprima": "2.7.3" + } + }, + "jsesc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-1.3.0.tgz", + "integrity": "sha1-RsP+yMGJKxKwgz25vHYiF226s0s=", + "dev": true + }, + "json-stable-stringify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz", + "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=", + "dev": true, + "requires": { + "jsonify": "0.0.0" + } + }, + "json3": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/json3/-/json3-3.3.2.tgz", + "integrity": "sha1-PAQ0dD35Pi9cQq7nsZvLSDV19OE=", + "dev": true + }, + "json5": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", + "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=", + "dev": true + }, + "jsonify": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", + "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=", + "dev": true + }, + "jsonpointer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-4.0.1.tgz", + "integrity": "sha1-T9kss04OnbPInIYi7PUfm5eMbLk=", + "dev": true + }, + "jsx-ast-utils": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-1.4.1.tgz", + "integrity": "sha1-OGchPo3Xm/Ho8jAMDPwe+xgsDfE=", + "dev": true + }, + "kew": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/kew/-/kew-0.5.0.tgz", + "integrity": "sha1-7OEctdjQGoH4zoBMjQu6BuayXKI=", + "dev": true + }, + "keycode": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/keycode/-/keycode-2.1.9.tgz", + "integrity": "sha1-lkojxU5IiUBbSGGlyfBIDUUUHfo=" + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "1.1.5" + } + }, + "lazy-cache": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", + "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=", + "dev": true + }, + "levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "dev": true, + "requires": { + "prelude-ls": "1.1.2", + "type-check": "0.3.2" + } + }, + "loader-utils": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-0.2.17.tgz", + "integrity": "sha1-+G5jdNQyBabmxg6RlvF8Apm/s0g=", + "dev": true, + "requires": { + "big.js": "3.1.3", + "emojis-list": "2.1.0", + "json5": "0.5.1", + "object-assign": "4.1.1" + } + }, + "lodash": { + "version": "4.17.4", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", + "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4=" + }, + "lodash-es": { + "version": "4.17.4", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.4.tgz", + "integrity": "sha1-3MHXVS4VCgZABzupyzHXDwMpUOc=" + }, + "lodash._baseassign": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/lodash._baseassign/-/lodash._baseassign-3.2.0.tgz", + "integrity": "sha1-jDigmVAPIVrQnlnxci/QxSv+Ck4=", + "dev": true, + "requires": { + "lodash._basecopy": "3.0.1", + "lodash.keys": "3.1.2" + } + }, + "lodash._basecopy": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz", + "integrity": "sha1-jaDmqHbPNEwK2KVIghEd08XHyjY=", + "dev": true + }, + "lodash._basecreate": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash._basecreate/-/lodash._basecreate-3.0.3.tgz", + "integrity": "sha1-G8ZhYU2qf8MRt9A78WgGoCE8+CE=", + "dev": true + }, + "lodash._getnative": { + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz", + "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=", + "dev": true + }, + "lodash._isiterateecall": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz", + "integrity": "sha1-UgOte6Ql+uhCRg5pbbnPPmqsBXw=", + "dev": true + }, + "lodash.assignin": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.assignin/-/lodash.assignin-4.2.0.tgz", + "integrity": "sha1-uo31+4QesKPoBEIysOJjqNxqKKI=", + "dev": true + }, + "lodash.bind": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/lodash.bind/-/lodash.bind-4.2.1.tgz", + "integrity": "sha1-euMBfpOWIqwxt9fX3LGzTbFpDTU=", + "dev": true + }, + "lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=", + "dev": true + }, + "lodash.cond": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/lodash.cond/-/lodash.cond-4.5.2.tgz", + "integrity": "sha1-9HGh2khr5g9quVXRcRVSPdHSVdU=", + "dev": true + }, + "lodash.create": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/lodash.create/-/lodash.create-3.1.1.tgz", + "integrity": "sha1-1/KEnw29p+BGgruM1yqwIkYd6+c=", + "dev": true, + "requires": { + "lodash._baseassign": "3.2.0", + "lodash._basecreate": "3.0.3", + "lodash._isiterateecall": "3.0.9" + } + }, + "lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=" + }, + "lodash.defaults": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", + "integrity": "sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw=", + "dev": true + }, + "lodash.defaultsdeep": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.defaultsdeep/-/lodash.defaultsdeep-4.6.0.tgz", + "integrity": "sha1-vsECT4WxvZbL6kBbI8FK1kQ6b4E=" + }, + "lodash.filter": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.filter/-/lodash.filter-4.6.0.tgz", + "integrity": "sha1-ZosdSYFgOuHMWm+nYBQ+SAtMSs4=", + "dev": true + }, + "lodash.flatten": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", + "integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=", + "dev": true + }, + "lodash.foreach": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.foreach/-/lodash.foreach-4.5.0.tgz", + "integrity": "sha1-Gmo16s5AEoDH8G3d7DUWWrJ+PlM=", + "dev": true + }, + "lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=" + }, + "lodash.isarguments": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", + "integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=", + "dev": true + }, + "lodash.isarray": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz", + "integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=", + "dev": true + }, + "lodash.keys": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", + "integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=", + "dev": true, + "requires": { + "lodash._getnative": "3.9.1", + "lodash.isarguments": "3.1.0", + "lodash.isarray": "3.0.4" + } + }, + "lodash.map": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.map/-/lodash.map-4.6.0.tgz", + "integrity": "sha1-dx7Hg540c9nEzeKLGTlMNWL09tM=", + "dev": true + }, + "lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=", + "dev": true + }, + "lodash.merge": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.0.tgz", + "integrity": "sha1-aYhLoUSsM/5plzemCG3v+t0PicU=" + }, + "lodash.pick": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.pick/-/lodash.pick-4.4.0.tgz", + "integrity": "sha1-UvBWEP/53tQiYRRB7R/BI6AwAbM=", + "dev": true + }, + "lodash.reduce": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.reduce/-/lodash.reduce-4.6.0.tgz", + "integrity": "sha1-8atrg5KZrUj3hKu/R2WW8DuRTTs=", + "dev": true + }, + "lodash.reject": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.reject/-/lodash.reject-4.6.0.tgz", + "integrity": "sha1-gNZJLcFHCGS79YNTO2UfQqn1JBU=", + "dev": true + }, + "lodash.set": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/lodash.set/-/lodash.set-4.3.2.tgz", + "integrity": "sha1-2HV7HagH3eJIFrDWqEvqGnYjCyM=" + }, + "lodash.some": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.some/-/lodash.some-4.6.0.tgz", + "integrity": "sha1-G7nzFO9ri63tE7VJFpsqlF62jk0=", + "dev": true + }, + "lodash.throttle": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz", + "integrity": "sha1-wj6RtxAkKscMN/HhzaknTMOb8vQ=" + }, + "lodash.uniq": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", + "integrity": "sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=", + "dev": true + }, + "lolex": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/lolex/-/lolex-1.6.0.tgz", + "integrity": "sha1-OpoCg0UqR9dDnnJzG54H1zhuSfY=", + "dev": true + }, + "longest": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", + "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=", + "dev": true + }, + "loose-envify": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.3.1.tgz", + "integrity": "sha1-0aitM/qc4OcT1l/dCsi3SNR4yEg=", + "requires": { + "js-tokens": "3.0.2" + } + }, + "macaddress": { + "version": "0.2.8", + "resolved": "https://registry.npmjs.org/macaddress/-/macaddress-0.2.8.tgz", + "integrity": "sha1-WQTcU3w57G2+/q6QIycTX6hRHxI=", + "dev": true + }, + "material-ui": { + "version": "0.17.4", + "resolved": "https://registry.npmjs.org/material-ui/-/material-ui-0.17.4.tgz", + "integrity": "sha1-GTmZ7LScPsFa4Ku06Q/fmnvTQ+A=", + "requires": { + "babel-runtime": "6.26.0", + "inline-style-prefixer": "3.0.7", + "keycode": "2.1.9", + "lodash.merge": "4.6.0", + "lodash.throttle": "4.1.1", + "prop-types": "15.5.10", + "react-addons-create-fragment": "15.6.0", + "react-addons-transition-group": "15.6.0", + "react-event-listener": "0.4.5", + "recompose": "0.23.5", + "simple-assign": "0.1.0", + "warning": "3.0.0" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "requires": { + "core-js": "2.5.0", + "regenerator-runtime": "0.11.0" + } + }, + "regenerator-runtime": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.0.tgz", + "integrity": "sha512-/aA0kLeRb5N9K0d4fw7ooEbI+xDe+DKD499EQqygGqeS8N3xto15p09uY2xj7ixP81sNPXvRLnAQIqdVStgb1A==" + } + } + }, + "material-ui-chip-input": { + "version": "0.13.6", + "resolved": "https://registry.npmjs.org/material-ui-chip-input/-/material-ui-chip-input-0.13.6.tgz", + "integrity": "sha1-yl9QyF2h96PgJTJRP5XMRx/RXTA=", + "requires": { + "prop-types": "15.5.10" + } + }, + "math-expression-evaluator": { + "version": "1.2.17", + "resolved": "https://registry.npmjs.org/math-expression-evaluator/-/math-expression-evaluator-1.2.17.tgz", + "integrity": "sha1-3oGf282E3M2PrlnGrreWFbnSZqw=", + "dev": true + }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", + "dev": true + }, + "memory-fs": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.3.0.tgz", + "integrity": "sha1-e8xrYp46Q+hx1+Kaymrop/FcuyA=", + "dev": true, + "requires": { + "errno": "0.1.4", + "readable-stream": "2.3.3" + } + }, + "merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=", + "dev": true + }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", + "dev": true + }, + "micromatch": { + "version": "2.3.11", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", + "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=", + "dev": true, + "requires": { + "arr-diff": "2.0.0", + "array-unique": "0.2.1", + "braces": "1.8.5", + "expand-brackets": "0.1.5", + "extglob": "0.3.2", + "filename-regex": "2.0.1", + "is-extglob": "1.0.0", + "is-glob": "2.0.1", + "kind-of": "3.2.2", + "normalize-path": "2.1.1", + "object.omit": "2.0.1", + "parse-glob": "3.0.4", + "regex-cache": "0.4.3" + } + }, + "mime": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.3.4.tgz", + "integrity": "sha1-EV+eO2s9rylZmDyzjxSaLUDrXVM=", + "dev": true + }, + "mime-db": { + "version": "1.29.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.29.0.tgz", + "integrity": "sha1-SNJtI1WJZRcErFkWygYAGRQmaHg=", + "dev": true + }, + "mime-types": { + "version": "2.1.16", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.16.tgz", + "integrity": "sha1-K4WKUuXs1RbbiXrCvodIeDBpjiM=", + "dev": true, + "requires": { + "mime-db": "1.29.0" + } + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "1.1.8" + } + }, + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true, + "requires": { + "minimist": "0.0.8" + } + }, + "mocha": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-3.2.0.tgz", + "integrity": "sha1-fcT0XlCIB1FxpoiWgU5q6et6heM=", + "dev": true, + "requires": { + "browser-stdout": "1.3.0", + "commander": "2.9.0", + "debug": "2.2.0", + "diff": "1.4.0", + "escape-string-regexp": "1.0.5", + "glob": "7.0.5", + "growl": "1.9.2", + "json3": "3.3.2", + "lodash.create": "3.1.1", + "mkdirp": "0.5.1", + "supports-color": "3.1.2" + }, + "dependencies": { + "commander": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz", + "integrity": "sha1-nJkJQXbhIkDLItbFFGCYQA/g99Q=", + "dev": true, + "requires": { + "graceful-readlink": "1.0.1" + } + }, + "debug": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", + "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", + "dev": true, + "requires": { + "ms": "0.7.1" + } + }, + "glob": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.0.5.tgz", + "integrity": "sha1-tCAqaQmbu00pKnwblbZoK2fr3JU=", + "dev": true, + "requires": { + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" + } + }, + "has-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "dev": true + }, + "ms": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", + "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=", + "dev": true + }, + "supports-color": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.1.2.tgz", + "integrity": "sha1-cqJiiU2dQIuVbKBf83su2KbiotU=", + "dev": true, + "requires": { + "has-flag": "1.0.0" + } + } + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "native-promise-only": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/native-promise-only/-/native-promise-only-0.8.1.tgz", + "integrity": "sha1-IKMYwwy0X3H+et+/eyHJnBRy7xE=", + "dev": true + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "dev": true + }, + "negotiator": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", + "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=", + "dev": true + }, + "node-fetch": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.2.tgz", + "integrity": "sha512-xZZUq2yDhKMIn/UgG5q//IZSNLJIwW2QxS14CNH5spuiXkITM2pUitjdq58yLSaU7m4M0wBNaM2Gh/ggY4YJig==", + "requires": { + "encoding": "0.1.12", + "is-stream": "1.1.0" + } + }, + "node-libs-browser": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-0.6.0.tgz", + "integrity": "sha1-JEgG1E0xngSLyGB7XMTq+aKdLjw=", + "dev": true, + "requires": { + "assert": "1.4.1", + "browserify-zlib": "0.1.4", + "buffer": "4.9.1", + "console-browserify": "1.1.0", + "constants-browserify": "0.0.1", + "crypto-browserify": "3.2.8", + "domain-browser": "1.1.7", + "events": "1.1.1", + "http-browserify": "1.7.0", + "https-browserify": "0.0.0", + "os-browserify": "0.1.2", + "path-browserify": "0.0.0", + "process": "0.11.10", + "punycode": "1.4.1", + "querystring-es3": "0.2.1", + "readable-stream": "1.1.14", + "stream-browserify": "1.0.0", + "string_decoder": "0.10.31", + "timers-browserify": "1.4.2", + "tty-browserify": "0.0.0", + "url": "0.10.3", + "util": "0.10.3", + "vm-browserify": "0.0.4" + }, + "dependencies": { + "readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "dev": true, + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "0.0.1", + "string_decoder": "0.10.31" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + } + } + }, + "node-polyglot": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/node-polyglot/-/node-polyglot-2.2.2.tgz", + "integrity": "sha1-Gj921zkvg26ggjg27egX5upuwmw=", + "requires": { + "for-each": "0.3.2", + "has": "1.0.1", + "string.prototype.trim": "1.1.2", + "warning": "3.0.0" + } + }, + "nopt": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", + "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=", + "dev": true, + "requires": { + "abbrev": "1.1.0" + } + }, + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "dev": true, + "requires": { + "remove-trailing-separator": "1.1.0" + } + }, + "normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha1-LRDAa9/TEuqXd2laTShDlFa3WUI=", + "dev": true + }, + "normalize-url": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-1.9.1.tgz", + "integrity": "sha1-LMDWazHqIwNkWENuNiDYWVTGbDw=", + "dev": true, + "requires": { + "object-assign": "4.1.1", + "prepend-http": "1.0.4", + "query-string": "4.3.4", + "sort-keys": "1.1.2" + } + }, + "npmconf": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/npmconf/-/npmconf-2.1.2.tgz", + "integrity": "sha1-ZmBqSnNvHnegWaoHGnnJSreBhTo=", + "dev": true, + "requires": { + "config-chain": "1.1.11", + "inherits": "2.0.3", + "ini": "1.3.4", + "mkdirp": "0.5.1", + "nopt": "3.0.6", + "once": "1.3.3", + "osenv": "0.1.4", + "semver": "4.3.6", + "uid-number": "0.0.5" + }, + "dependencies": { + "once": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/once/-/once-1.3.3.tgz", + "integrity": "sha1-suJhVXzkwxTsgwTz+oJmPkKXyiA=", + "dev": true, + "requires": { + "wrappy": "1.0.2" + } + } + } + }, + "nth-check": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.1.tgz", + "integrity": "sha1-mSms32KPwsQQmN6rgqxYDPFJquQ=", + "dev": true, + "requires": { + "boolbase": "1.0.0" + } + }, + "num2fraction": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/num2fraction/-/num2fraction-1.2.2.tgz", + "integrity": "sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4=", + "dev": true + }, + "number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", + "dev": true + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + }, + "object-is": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.0.1.tgz", + "integrity": "sha1-CqYOyZiaCz7Xlc9NBvYs8a1lObY=", + "dev": true + }, + "object-keys": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.0.11.tgz", + "integrity": "sha1-xUYBd4rVYPEULODgG8yotW0TQm0=" + }, + "object.assign": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.0.4.tgz", + "integrity": "sha1-scnMBE7xuf5jYG/BQau7MuFHMMw=", + "dev": true, + "requires": { + "define-properties": "1.1.2", + "function-bind": "1.1.0", + "object-keys": "1.0.11" + } + }, + "object.entries": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.0.4.tgz", + "integrity": "sha1-G/mk3SKI9bM/Opk9JXZh8F0WGl8=", + "dev": true, + "requires": { + "define-properties": "1.1.2", + "es-abstract": "1.8.0", + "function-bind": "1.1.0", + "has": "1.0.1" + } + }, + "object.omit": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.1.tgz", + "integrity": "sha1-Gpx0SCnznbuFjHbKNXmuKlTr0fo=", + "dev": true, + "requires": { + "for-own": "0.1.5", + "is-extendable": "0.1.1" + } + }, + "object.values": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.0.4.tgz", + "integrity": "sha1-5STaCbT2b/Bd9FdUbscqyZ8TBpo=", + "dev": true, + "requires": { + "define-properties": "1.1.2", + "es-abstract": "1.8.0", + "function-bind": "1.1.0", + "has": "1.0.1" + } + }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "dev": true, + "requires": { + "ee-first": "1.1.1" + } + }, + "on-headers": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.1.tgz", + "integrity": "sha1-ko9dD0cNSTQmUepnlLCFfBAGk/c=", + "dev": true + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1.0.2" + } + }, + "onetime": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz", + "integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=", + "dev": true + }, + "open": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/open/-/open-0.0.5.tgz", + "integrity": "sha1-QsPhjslUZra/DcQvOilFw/DK2Pw=", + "dev": true + }, + "optimist": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", + "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", + "dev": true, + "requires": { + "minimist": "0.0.8", + "wordwrap": "0.0.3" + }, + "dependencies": { + "wordwrap": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", + "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=", + "dev": true + } + } + }, + "optionator": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", + "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", + "dev": true, + "requires": { + "deep-is": "0.1.3", + "fast-levenshtein": "2.0.6", + "levn": "0.3.0", + "prelude-ls": "1.1.2", + "type-check": "0.3.2", + "wordwrap": "1.0.0" + } + }, + "original": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/original/-/original-1.0.0.tgz", + "integrity": "sha1-kUf5P6FpbQS+YeAb1QuurKZWvTs=", + "dev": true, + "requires": { + "url-parse": "1.0.5" + }, + "dependencies": { + "url-parse": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.0.5.tgz", + "integrity": "sha1-CFSGBCKv3P7+tsllxmLUgAFpkns=", + "dev": true, + "requires": { + "querystringify": "0.0.4", + "requires-port": "1.0.0" + } + } + } + }, + "os-browserify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.1.2.tgz", + "integrity": "sha1-ScoCk+CxlZCl9d4Qx/JlphfY/lQ=", + "dev": true + }, + "os-homedir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", + "dev": true + }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", + "dev": true + }, + "osenv": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.4.tgz", + "integrity": "sha1-Qv5tWVPfBsgGS+bxdsPQWqqjRkQ=", + "dev": true, + "requires": { + "os-homedir": "1.0.2", + "os-tmpdir": "1.0.2" + } + }, + "output-file-sync": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/output-file-sync/-/output-file-sync-1.1.2.tgz", + "integrity": "sha1-0KM+7+YaIF+suQCS6CZZjVJFznY=", + "dev": true, + "requires": { + "graceful-fs": "4.1.11", + "mkdirp": "0.5.1", + "object-assign": "4.1.1" + } + }, + "pako": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", + "integrity": "sha1-8/dSL073gjSNqBYbrZ7P1Rv4OnU=", + "dev": true + }, + "parse-glob": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz", + "integrity": "sha1-ssN2z7EfNVE7rdFz7wu246OIORw=", + "dev": true, + "requires": { + "glob-base": "0.3.0", + "is-dotfile": "1.0.3", + "is-extglob": "1.0.0", + "is-glob": "2.0.1" + } + }, + "parseurl": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.1.tgz", + "integrity": "sha1-yKuMkiO6NIiKpkopeyiFO+wY2lY=", + "dev": true + }, + "path-browserify": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.0.tgz", + "integrity": "sha1-oLhwcpquIUAFt9UDLsLLuw+0RRo=", + "dev": true + }, + "path-exists": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", + "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", + "dev": true, + "requires": { + "pinkie-promise": "2.0.1" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", + "dev": true + }, + "path-parse": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.5.tgz", + "integrity": "sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME=", + "dev": true + }, + "path-to-regexp": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.7.0.tgz", + "integrity": "sha1-Wf3g9DW62suhA6hOnTvGTpa5k30=", + "requires": { + "isarray": "0.0.1" + } + }, + "pbkdf2-compat": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pbkdf2-compat/-/pbkdf2-compat-2.0.1.tgz", + "integrity": "sha1-tuDI+plJTZTgURV1gCpZpcFC8og=", + "dev": true + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + }, + "pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", + "dev": true + }, + "pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "dev": true, + "requires": { + "pinkie": "2.0.4" + } + }, + "pkg-dir": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-1.0.0.tgz", + "integrity": "sha1-ektQio1bstYp1EcFb/TpyTFM89Q=", + "dev": true, + "requires": { + "find-up": "1.1.2" + } + }, + "pkg-up": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-1.0.0.tgz", + "integrity": "sha1-Pgj7RhUlxEIWJKM7n35tCvWwWiY=", + "dev": true, + "requires": { + "find-up": "1.1.2" + } + }, + "pluralize": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-1.2.1.tgz", + "integrity": "sha1-0aIUg/0iu0HlihL6NCGCMUCJfEU=", + "dev": true + }, + "postcss": { + "version": "5.2.17", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.17.tgz", + "integrity": "sha1-z09Ze4ZNZcikkrLqvp1wbIecOIs=", + "dev": true, + "requires": { + "chalk": "1.1.3", + "js-base64": "2.1.9", + "source-map": "0.5.7", + "supports-color": "3.2.3" + }, + "dependencies": { + "supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "dev": true, + "requires": { + "has-flag": "1.0.0" + } + } + } + }, + "postcss-calc": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-5.3.1.tgz", + "integrity": "sha1-d7rnypKK2FcW4v2kLyYb98HWW14=", + "dev": true, + "requires": { + "postcss": "5.2.17", + "postcss-message-helpers": "2.0.0", + "reduce-css-calc": "1.3.0" + } + }, + "postcss-colormin": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-2.2.2.tgz", + "integrity": "sha1-ZjFBfV8OkJo9fsJrJMio0eT5bks=", + "dev": true, + "requires": { + "colormin": "1.1.2", + "postcss": "5.2.17", + "postcss-value-parser": "3.3.0" + } + }, + "postcss-convert-values": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-2.6.1.tgz", + "integrity": "sha1-u9hZPFwf0uPRwyK7kl3K6Nrk1i0=", + "dev": true, + "requires": { + "postcss": "5.2.17", + "postcss-value-parser": "3.3.0" + } + }, + "postcss-discard-comments": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-2.0.4.tgz", + "integrity": "sha1-vv6J+v1bPazlzM5Rt2uBUUvgDj0=", + "dev": true, + "requires": { + "postcss": "5.2.17" + } + }, + "postcss-discard-duplicates": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-2.1.0.tgz", + "integrity": "sha1-uavye4isGIFYpesSq8riAmO5GTI=", + "dev": true, + "requires": { + "postcss": "5.2.17" + } + }, + "postcss-discard-empty": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-2.1.0.tgz", + "integrity": "sha1-0rS9nVztXr2Nyt52QMfXzX9PkrU=", + "dev": true, + "requires": { + "postcss": "5.2.17" + } + }, + "postcss-discard-overridden": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-0.1.1.tgz", + "integrity": "sha1-ix6vVU9ob7KIzYdMVWZ7CqNmjVg=", + "dev": true, + "requires": { + "postcss": "5.2.17" + } + }, + "postcss-discard-unused": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/postcss-discard-unused/-/postcss-discard-unused-2.2.3.tgz", + "integrity": "sha1-vOMLLMWR/8Y0Mitfs0ZLbZNPRDM=", + "dev": true, + "requires": { + "postcss": "5.2.17", + "uniqs": "2.0.0" + } + }, + "postcss-filter-plugins": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/postcss-filter-plugins/-/postcss-filter-plugins-2.0.2.tgz", + "integrity": "sha1-bYWGJTTXNaxCDkqFgG4fXUKG2Ew=", + "dev": true, + "requires": { + "postcss": "5.2.17", + "uniqid": "4.1.1" + } + }, + "postcss-merge-idents": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/postcss-merge-idents/-/postcss-merge-idents-2.1.7.tgz", + "integrity": "sha1-TFUwMTwI4dWzu/PSu8dH4njuonA=", + "dev": true, + "requires": { + "has": "1.0.1", + "postcss": "5.2.17", + "postcss-value-parser": "3.3.0" + } + }, + "postcss-merge-longhand": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-2.0.2.tgz", + "integrity": "sha1-I9kM0Sewp3mUkVMyc5A0oaTz1lg=", + "dev": true, + "requires": { + "postcss": "5.2.17" + } + }, + "postcss-merge-rules": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-2.1.2.tgz", + "integrity": "sha1-0d9d+qexrMO+VT8OnhDofGG19yE=", + "dev": true, + "requires": { + "browserslist": "1.7.7", + "caniuse-api": "1.6.1", + "postcss": "5.2.17", + "postcss-selector-parser": "2.2.3", + "vendors": "1.0.1" + } + }, + "postcss-message-helpers": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postcss-message-helpers/-/postcss-message-helpers-2.0.0.tgz", + "integrity": "sha1-pPL0+rbk/gAvCu0ABHjN9S+bpg4=", + "dev": true + }, + "postcss-minify-font-values": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-1.0.5.tgz", + "integrity": "sha1-S1jttWZB66fIR0qzUmyv17vey2k=", + "dev": true, + "requires": { + "object-assign": "4.1.1", + "postcss": "5.2.17", + "postcss-value-parser": "3.3.0" + } + }, + "postcss-minify-gradients": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-1.0.5.tgz", + "integrity": "sha1-Xb2hE3NwP4PPtKPqOIHY11/15uE=", + "dev": true, + "requires": { + "postcss": "5.2.17", + "postcss-value-parser": "3.3.0" + } + }, + "postcss-minify-params": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-1.2.2.tgz", + "integrity": "sha1-rSzgcTc7lDs9kwo/pZo1jCjW8fM=", + "dev": true, + "requires": { + "alphanum-sort": "1.0.2", + "postcss": "5.2.17", + "postcss-value-parser": "3.3.0", + "uniqs": "2.0.0" + } + }, + "postcss-minify-selectors": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-2.1.1.tgz", + "integrity": "sha1-ssapjAByz5G5MtGkllCBFDEXNb8=", + "dev": true, + "requires": { + "alphanum-sort": "1.0.2", + "has": "1.0.1", + "postcss": "5.2.17", + "postcss-selector-parser": "2.2.3" + } + }, + "postcss-modules-extract-imports": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-1.1.0.tgz", + "integrity": "sha1-thTJcgvmgW6u41+zpfqh26agXds=", + "dev": true, + "requires": { + "postcss": "6.0.9" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz", + "integrity": "sha512-NnSOmMEYtVR2JVMIGTzynRkkaxtiq1xnFBcdQD/DnNCYPoEPsVJhM98BDyaoNOQIi7p4okdi3E27eN7GQbsUug==", + "dev": true, + "requires": { + "color-convert": "1.9.0" + } + }, + "chalk": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.1.0.tgz", + "integrity": "sha512-LUHGS/dge4ujbXMJrnihYMcL4AoOweGnw9Tp3kQuqy1Kx5c1qKjqvMJZ6nVJPMWJtKCTN72ZogH3oeSO9g9rXQ==", + "dev": true, + "requires": { + "ansi-styles": "3.2.0", + "escape-string-regexp": "1.0.5", + "supports-color": "4.2.1" + } + }, + "has-flag": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", + "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=", + "dev": true + }, + "postcss": { + "version": "6.0.9", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.9.tgz", + "integrity": "sha512-bBE2AHNEBhF23TfET6AA/lFP8ah+qHOZoFJEflFG+HgvVLdTmMOrocx/4LVVDIn3w6jUssw1q2Exk1cc9UOI8w==", + "dev": true, + "requires": { + "chalk": "2.1.0", + "source-map": "0.5.7", + "supports-color": "4.2.1" + } + }, + "supports-color": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.2.1.tgz", + "integrity": "sha512-qxzYsob3yv6U+xMzPrv170y8AwGP7i74g+pbixCfD6rgso8BscLT2qXIuz6TpOaiJZ3mFgT5O9lyT9nMU4LfaA==", + "dev": true, + "requires": { + "has-flag": "2.0.0" + } + } + } + }, + "postcss-modules-local-by-default": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-1.2.0.tgz", + "integrity": "sha1-99gMOYxaOT+nlkRmvRlQCn1hwGk=", + "dev": true, + "requires": { + "css-selector-tokenizer": "0.7.0", + "postcss": "6.0.9" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz", + "integrity": "sha512-NnSOmMEYtVR2JVMIGTzynRkkaxtiq1xnFBcdQD/DnNCYPoEPsVJhM98BDyaoNOQIi7p4okdi3E27eN7GQbsUug==", + "dev": true, + "requires": { + "color-convert": "1.9.0" + } + }, + "chalk": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.1.0.tgz", + "integrity": "sha512-LUHGS/dge4ujbXMJrnihYMcL4AoOweGnw9Tp3kQuqy1Kx5c1qKjqvMJZ6nVJPMWJtKCTN72ZogH3oeSO9g9rXQ==", + "dev": true, + "requires": { + "ansi-styles": "3.2.0", + "escape-string-regexp": "1.0.5", + "supports-color": "4.2.1" + } + }, + "has-flag": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", + "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=", + "dev": true + }, + "postcss": { + "version": "6.0.9", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.9.tgz", + "integrity": "sha512-bBE2AHNEBhF23TfET6AA/lFP8ah+qHOZoFJEflFG+HgvVLdTmMOrocx/4LVVDIn3w6jUssw1q2Exk1cc9UOI8w==", + "dev": true, + "requires": { + "chalk": "2.1.0", + "source-map": "0.5.7", + "supports-color": "4.2.1" + } + }, + "supports-color": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.2.1.tgz", + "integrity": "sha512-qxzYsob3yv6U+xMzPrv170y8AwGP7i74g+pbixCfD6rgso8BscLT2qXIuz6TpOaiJZ3mFgT5O9lyT9nMU4LfaA==", + "dev": true, + "requires": { + "has-flag": "2.0.0" + } + } + } + }, + "postcss-modules-scope": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-1.1.0.tgz", + "integrity": "sha1-1upkmUx5+XtipytCb75gVqGUu5A=", + "dev": true, + "requires": { + "css-selector-tokenizer": "0.7.0", + "postcss": "6.0.9" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz", + "integrity": "sha512-NnSOmMEYtVR2JVMIGTzynRkkaxtiq1xnFBcdQD/DnNCYPoEPsVJhM98BDyaoNOQIi7p4okdi3E27eN7GQbsUug==", + "dev": true, + "requires": { + "color-convert": "1.9.0" + } + }, + "chalk": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.1.0.tgz", + "integrity": "sha512-LUHGS/dge4ujbXMJrnihYMcL4AoOweGnw9Tp3kQuqy1Kx5c1qKjqvMJZ6nVJPMWJtKCTN72ZogH3oeSO9g9rXQ==", + "dev": true, + "requires": { + "ansi-styles": "3.2.0", + "escape-string-regexp": "1.0.5", + "supports-color": "4.2.1" + } + }, + "has-flag": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", + "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=", + "dev": true + }, + "postcss": { + "version": "6.0.9", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.9.tgz", + "integrity": "sha512-bBE2AHNEBhF23TfET6AA/lFP8ah+qHOZoFJEflFG+HgvVLdTmMOrocx/4LVVDIn3w6jUssw1q2Exk1cc9UOI8w==", + "dev": true, + "requires": { + "chalk": "2.1.0", + "source-map": "0.5.7", + "supports-color": "4.2.1" + } + }, + "supports-color": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.2.1.tgz", + "integrity": "sha512-qxzYsob3yv6U+xMzPrv170y8AwGP7i74g+pbixCfD6rgso8BscLT2qXIuz6TpOaiJZ3mFgT5O9lyT9nMU4LfaA==", + "dev": true, + "requires": { + "has-flag": "2.0.0" + } + } + } + }, + "postcss-modules-values": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-1.3.0.tgz", + "integrity": "sha1-7P+p1+GSUYOJ9CrQ6D9yrsRW6iA=", + "dev": true, + "requires": { + "icss-replace-symbols": "1.1.0", + "postcss": "6.0.9" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz", + "integrity": "sha512-NnSOmMEYtVR2JVMIGTzynRkkaxtiq1xnFBcdQD/DnNCYPoEPsVJhM98BDyaoNOQIi7p4okdi3E27eN7GQbsUug==", + "dev": true, + "requires": { + "color-convert": "1.9.0" + } + }, + "chalk": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.1.0.tgz", + "integrity": "sha512-LUHGS/dge4ujbXMJrnihYMcL4AoOweGnw9Tp3kQuqy1Kx5c1qKjqvMJZ6nVJPMWJtKCTN72ZogH3oeSO9g9rXQ==", + "dev": true, + "requires": { + "ansi-styles": "3.2.0", + "escape-string-regexp": "1.0.5", + "supports-color": "4.2.1" + } + }, + "has-flag": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", + "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=", + "dev": true + }, + "postcss": { + "version": "6.0.9", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.9.tgz", + "integrity": "sha512-bBE2AHNEBhF23TfET6AA/lFP8ah+qHOZoFJEflFG+HgvVLdTmMOrocx/4LVVDIn3w6jUssw1q2Exk1cc9UOI8w==", + "dev": true, + "requires": { + "chalk": "2.1.0", + "source-map": "0.5.7", + "supports-color": "4.2.1" + } + }, + "supports-color": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.2.1.tgz", + "integrity": "sha512-qxzYsob3yv6U+xMzPrv170y8AwGP7i74g+pbixCfD6rgso8BscLT2qXIuz6TpOaiJZ3mFgT5O9lyT9nMU4LfaA==", + "dev": true, + "requires": { + "has-flag": "2.0.0" + } + } + } + }, + "postcss-normalize-charset": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-1.1.1.tgz", + "integrity": "sha1-757nEhLX/nWceO0WL2HtYrXLk/E=", + "dev": true, + "requires": { + "postcss": "5.2.17" + } + }, + "postcss-normalize-url": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-3.0.8.tgz", + "integrity": "sha1-EI90s/L82viRov+j6kWSJ5/HgiI=", + "dev": true, + "requires": { + "is-absolute-url": "2.1.0", + "normalize-url": "1.9.1", + "postcss": "5.2.17", + "postcss-value-parser": "3.3.0" + } + }, + "postcss-ordered-values": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-2.2.3.tgz", + "integrity": "sha1-7sbCpntsQSqNsgQud/6NpD+VwR0=", + "dev": true, + "requires": { + "postcss": "5.2.17", + "postcss-value-parser": "3.3.0" + } + }, + "postcss-reduce-idents": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/postcss-reduce-idents/-/postcss-reduce-idents-2.4.0.tgz", + "integrity": "sha1-wsbSDMlYKE9qv75j92Cb9AkFmtM=", + "dev": true, + "requires": { + "postcss": "5.2.17", + "postcss-value-parser": "3.3.0" + } + }, + "postcss-reduce-initial": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-1.0.1.tgz", + "integrity": "sha1-aPgGlfBF0IJjqHmtJA343WT2ROo=", + "dev": true, + "requires": { + "postcss": "5.2.17" + } + }, + "postcss-reduce-transforms": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-1.0.4.tgz", + "integrity": "sha1-/3b02CEkN7McKYpC0uFEQCV3GuE=", + "dev": true, + "requires": { + "has": "1.0.1", + "postcss": "5.2.17", + "postcss-value-parser": "3.3.0" + } + }, + "postcss-selector-parser": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-2.2.3.tgz", + "integrity": "sha1-+UN3iGBsPJrO4W/+jYsWKX8nu5A=", + "dev": true, + "requires": { + "flatten": "1.0.2", + "indexes-of": "1.0.1", + "uniq": "1.0.1" + } + }, + "postcss-svgo": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-2.1.6.tgz", + "integrity": "sha1-tt8YqmE7Zm4TPwittSGcJoSsEI0=", + "dev": true, + "requires": { + "is-svg": "2.1.0", + "postcss": "5.2.17", + "postcss-value-parser": "3.3.0", + "svgo": "0.7.2" + } + }, + "postcss-unique-selectors": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-2.0.2.tgz", + "integrity": "sha1-mB1X0p3csz57Hf4f1DuGSfkzyh0=", + "dev": true, + "requires": { + "alphanum-sort": "1.0.2", + "postcss": "5.2.17", + "uniqs": "2.0.0" + } + }, + "postcss-value-parser": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.0.tgz", + "integrity": "sha1-h/OPnxj3dKSrTIojL1xc6IcqnRU=", + "dev": true + }, + "postcss-zindex": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/postcss-zindex/-/postcss-zindex-2.2.0.tgz", + "integrity": "sha1-0hCd3AVbka9n/EyzsCWUZjnSryI=", + "dev": true, + "requires": { + "has": "1.0.1", + "postcss": "5.2.17", + "uniqs": "2.0.0" + } + }, + "prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", + "dev": true + }, + "prepend-http": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", + "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=", + "dev": true + }, + "preserve": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz", + "integrity": "sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks=", + "dev": true + }, + "prettier": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.6.1.tgz", + "integrity": "sha512-f85qBoQiqiFM/sCmJaN4Lagj9bqMcv38vCftqp4GfVessAqq3Ns6g+3gd8UXReStLLE/DGEdwiZXoFKxphKqwg==", + "dev": true + }, + "private": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/private/-/private-0.1.7.tgz", + "integrity": "sha1-aM5eih7woju1cMwoU3tTMqumPvE=", + "dev": true + }, + "process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=", + "dev": true + }, + "process-nextick-args": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", + "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=", + "dev": true + }, + "progress": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/progress/-/progress-1.1.8.tgz", + "integrity": "sha1-4mDHj2Fhzdmw5WzD4Khd4Xx6V74=", + "dev": true + }, + "promise": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", + "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", + "requires": { + "asap": "2.0.6" + } + }, + "prop-types": { + "version": "15.5.10", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.5.10.tgz", + "integrity": "sha1-J5ffwxJhguOpXj37suiT3ddFYVQ=", + "requires": { + "fbjs": "0.8.14", + "loose-envify": "1.3.1" + } + }, + "proto-list": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", + "integrity": "sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk=", + "dev": true + }, + "proxy-addr": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-1.1.5.tgz", + "integrity": "sha1-ccDuOxAt4/IC87ZPYI0XP8uhqRg=", + "dev": true, + "requires": { + "forwarded": "0.1.0", + "ipaddr.js": "1.4.0" + } + }, + "prr": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/prr/-/prr-0.0.0.tgz", + "integrity": "sha1-GoS4WQgyVQFBGFPQCB7j+obikmo=", + "dev": true + }, + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", + "dev": true + }, + "q": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/q/-/q-1.5.0.tgz", + "integrity": "sha1-3QG6ydBtMObyGa7LglPunr3DCPE=", + "dev": true + }, + "qs": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.0.tgz", + "integrity": "sha512-fjVFjW9yhqMhVGwRExCXLhJKrLlkYSaxNWdyc9rmHlrVZbk35YHH312dFd7191uQeXkI3mKLZTIbSvIeFwFemg==", + "dev": true + }, + "query-string": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-4.3.4.tgz", + "integrity": "sha1-u7aTucqRXCMlFbIosaArYJBD2+s=", + "requires": { + "object-assign": "4.1.1", + "strict-uri-encode": "1.1.0" + } + }, + "querystring": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", + "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", + "dev": true + }, + "querystring-es3": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz", + "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=", + "dev": true + }, + "querystringify": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-0.0.4.tgz", + "integrity": "sha1-DPf4T5Rj/wrlHExLFC2VvjdyTZw=", + "dev": true + }, + "randomatic": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-1.1.7.tgz", + "integrity": "sha512-D5JUjPyJbaJDkuAazpVnSfVkLlpeO3wDlPROTMLGKG1zMFNFRgrciKo1ltz/AzNTkqE0HzDx655QOL51N06how==", + "dev": true, + "requires": { + "is-number": "3.0.0", + "kind-of": "4.0.0" + }, + "dependencies": { + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "1.1.5" + } + } + } + }, + "kind-of": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", + "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", + "dev": true, + "requires": { + "is-buffer": "1.1.5" + } + } + } + }, + "range-parser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", + "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=", + "dev": true + }, + "react": { + "version": "15.5.4", + "resolved": "https://registry.npmjs.org/react/-/react-15.5.4.tgz", + "integrity": "sha1-+oPrAVBqsjfNwcjDsc6o3gEr8Ec=", + "requires": { + "fbjs": "0.8.14", + "loose-envify": "1.3.1", + "object-assign": "4.1.1", + "prop-types": "15.5.10" + }, + "dependencies": { + "asap": { + "version": "2.0.6", + "bundled": true + }, + "core-js": { + "version": "1.2.7", + "bundled": true + }, + "encoding": { + "version": "0.1.12", + "bundled": true, + "requires": { + "iconv-lite": "0.4.18" + } + }, + "fbjs": { + "version": "0.8.14", + "bundled": true, + "requires": { + "core-js": "1.2.7", + "isomorphic-fetch": "2.2.1", + "loose-envify": "1.3.1", + "object-assign": "4.1.1", + "promise": "7.3.1", + "setimmediate": "1.0.5", + "ua-parser-js": "0.7.14" + } + }, + "iconv-lite": { + "version": "0.4.18", + "bundled": true + }, + "is-stream": { + "version": "1.1.0", + "bundled": true + }, + "isomorphic-fetch": { + "version": "2.2.1", + "bundled": true, + "requires": { + "node-fetch": "1.7.2", + "whatwg-fetch": "2.0.3" + } + }, + "js-tokens": { + "version": "3.0.2", + "bundled": true + }, + "loose-envify": { + "version": "1.3.1", + "bundled": true, + "requires": { + "js-tokens": "3.0.2" + } + }, + "node-fetch": { + "version": "1.7.2", + "bundled": true, + "requires": { + "encoding": "0.1.12", + "is-stream": "1.1.0" + } + }, + "object-assign": { + "version": "4.1.1", + "bundled": true + }, + "promise": { + "version": "7.3.1", + "bundled": true, + "requires": { + "asap": "2.0.6" + } + }, + "prop-types": { + "version": "15.5.10", + "bundled": true, + "requires": { + "fbjs": "0.8.14", + "loose-envify": "1.3.1" + } + }, + "setimmediate": { + "version": "1.0.5", + "bundled": true + }, + "ua-parser-js": { + "version": "0.7.14", + "bundled": true + }, + "whatwg-fetch": { + "version": "2.0.3", + "bundled": true + } + } + }, + "react-addons-create-fragment": { + "version": "15.6.0", + "resolved": "https://registry.npmjs.org/react-addons-create-fragment/-/react-addons-create-fragment-15.6.0.tgz", + "integrity": "sha1-r5GiKx+wld0B8a+6Q7/Q71idiyA=", + "requires": { + "fbjs": "0.8.14", + "loose-envify": "1.3.1", + "object-assign": "4.1.1" + } + }, + "react-addons-test-utils": { + "version": "15.5.1", + "resolved": "https://registry.npmjs.org/react-addons-test-utils/-/react-addons-test-utils-15.5.1.tgz", + "integrity": "sha1-4NJYzaKhIq0N/2n4OCYNDDlY9fc=", + "dev": true, + "requires": { + "fbjs": "0.8.14", + "object-assign": "4.1.1" + } + }, + "react-addons-transition-group": { + "version": "15.6.0", + "resolved": "https://registry.npmjs.org/react-addons-transition-group/-/react-addons-transition-group-15.6.0.tgz", + "integrity": "sha1-DyILn5WX2zqAqI29b+gF/GRM4hw=", + "requires": { + "react-transition-group": "1.2.0" + } + }, + "react-dom": { + "version": "15.5.4", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-15.5.4.tgz", + "integrity": "sha1-ugwoeG/VLtfk8hNf4CiNRirvk9o=", + "requires": { + "fbjs": "0.8.14", + "loose-envify": "1.3.1", + "object-assign": "4.1.1", + "prop-types": "15.5.10" + } + }, + "react-dropzone": { + "version": "3.13.4", + "resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-3.13.4.tgz", + "integrity": "sha1-hNomgVxAM5aRxJtFRMLvehaRLMw=", + "requires": { + "attr-accept": "1.1.0", + "prop-types": "15.5.10" + } + }, + "react-event-listener": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/react-event-listener/-/react-event-listener-0.4.5.tgz", + "integrity": "sha1-4+iVoJcM8U7o+JAROvaBl6vz0LE=", + "requires": { + "babel-runtime": "6.26.0", + "fbjs": "0.8.14", + "prop-types": "15.5.10", + "warning": "3.0.0" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "requires": { + "core-js": "2.5.0", + "regenerator-runtime": "0.11.0" + } + }, + "regenerator-runtime": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.0.tgz", + "integrity": "sha512-/aA0kLeRb5N9K0d4fw7ooEbI+xDe+DKD499EQqygGqeS8N3xto15p09uY2xj7ixP81sNPXvRLnAQIqdVStgb1A==" + } + } + }, + "react-redux": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-5.0.6.tgz", + "integrity": "sha512-8taaaGu+J7PMJQDJrk/xiWEYQmdo3mkXw6wPr3K3LxvXis3Fymiq7c13S+Tpls/AyNUAsoONkU81AP0RA6y6Vw==", + "requires": { + "hoist-non-react-statics": "2.3.0", + "invariant": "2.2.2", + "lodash": "4.17.4", + "lodash-es": "4.17.4", + "loose-envify": "1.3.1", + "prop-types": "15.5.10" + }, + "dependencies": { + "hoist-non-react-statics": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-2.3.0.tgz", + "integrity": "sha1-7eFjGML/H5/joCU5a6Bv1MRGCLs=" + } + } + }, + "react-router": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-4.1.2.tgz", + "integrity": "sha512-VyM87OP+GkijVkkOXJw39A9fKtFelLoZYYDxtELhpZefjYatxI2SUxZcImo/9Tv52rR9UnNJBPSBpVRQMdvi8A==", + "requires": { + "history": "4.6.3", + "hoist-non-react-statics": "1.2.0", + "invariant": "2.2.2", + "loose-envify": "1.3.1", + "path-to-regexp": "1.7.0", + "prop-types": "15.5.10", + "warning": "3.0.0" + } + }, + "react-router-dom": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-4.1.2.tgz", + "integrity": "sha512-CU6pFlpfvIj/xi36rZAbUiN0x39241q+d5bAfJJLtlEqlM62F3zgyv5aERH9zesmKqyDBBp2kd85rkq9Mo/iNQ==", + "requires": { + "history": "4.6.3", + "loose-envify": "1.3.1", + "prop-types": "15.5.10", + "react-router": "4.1.2" + } + }, + "react-router-redux": { + "version": "5.0.0-alpha.6", + "resolved": "https://registry.npmjs.org/react-router-redux/-/react-router-redux-5.0.0-alpha.6.tgz", + "integrity": "sha1-dBhmPC7NPFG+hW/PKPPR3uzBpXY=", + "requires": { + "history": "4.6.3", + "prop-types": "15.5.10", + "react-router": "4.1.2" + } + }, + "react-tap-event-plugin": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/react-tap-event-plugin/-/react-tap-event-plugin-2.0.1.tgz", + "integrity": "sha1-MWvrO8ZVbinshppyk+icgmqQdNI=", + "requires": { + "fbjs": "0.8.14" + } + }, + "react-transition-group": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-1.2.0.tgz", + "integrity": "sha1-tR/JIbDDg1p+98Vxx5/ILHPpIE8=", + "requires": { + "chain-function": "1.0.0", + "dom-helpers": "3.2.1", + "loose-envify": "1.3.1", + "prop-types": "15.5.10", + "warning": "3.0.0" + } + }, + "readable-stream": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", + "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==", + "dev": true, + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "1.0.7", + "safe-buffer": "5.1.1", + "string_decoder": "1.0.3", + "util-deprecate": "1.0.2" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + } + } + }, + "readdirp": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.1.0.tgz", + "integrity": "sha1-TtCtBg3zBzMAxIRANz9y0cxkLXg=", + "dev": true, + "requires": { + "graceful-fs": "4.1.11", + "minimatch": "3.0.4", + "readable-stream": "2.3.3", + "set-immediate-shim": "1.0.1" + } + }, + "readline2": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/readline2/-/readline2-1.0.1.tgz", + "integrity": "sha1-QQWWCP/BVHV7cV2ZidGZ/783LjU=", + "dev": true, + "requires": { + "code-point-at": "1.1.0", + "is-fullwidth-code-point": "1.0.0", + "mute-stream": "0.0.5" + }, + "dependencies": { + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, + "requires": { + "number-is-nan": "1.0.1" + } + }, + "mute-stream": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.5.tgz", + "integrity": "sha1-j7+rsKmKJT0xhDMfno3rc3L6xsA=", + "dev": true + } + } + }, + "rechoir": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", + "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", + "dev": true, + "requires": { + "resolve": "1.4.0" + } + }, + "recompose": { + "version": "0.23.5", + "resolved": "https://registry.npmjs.org/recompose/-/recompose-0.23.5.tgz", + "integrity": "sha1-cqyCYSRr7DeCNdGHRn0CpyHosd4=", + "requires": { + "change-emitter": "0.1.6", + "fbjs": "0.8.14", + "hoist-non-react-statics": "1.2.0", + "symbol-observable": "1.0.4" + } + }, + "reduce-css-calc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/reduce-css-calc/-/reduce-css-calc-1.3.0.tgz", + "integrity": "sha1-dHyRTgSWFKTJz7umKYca0dKSdxY=", + "dev": true, + "requires": { + "balanced-match": "0.4.2", + "math-expression-evaluator": "1.2.17", + "reduce-function-call": "1.0.2" + }, + "dependencies": { + "balanced-match": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz", + "integrity": "sha1-yz8+PHMtwPAe5wtAPzAuYddwmDg=", + "dev": true + } + } + }, + "reduce-function-call": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/reduce-function-call/-/reduce-function-call-1.0.2.tgz", + "integrity": "sha1-WiAL+S4ON3UXUv5FsKszD9S2vpk=", + "dev": true, + "requires": { + "balanced-match": "0.4.2" + }, + "dependencies": { + "balanced-match": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz", + "integrity": "sha1-yz8+PHMtwPAe5wtAPzAuYddwmDg=", + "dev": true + } + } + }, + "redux": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/redux/-/redux-3.6.0.tgz", + "integrity": "sha1-iHwrPQub2G7KK+cFccJ2VMGeGI0=", + "requires": { + "lodash": "4.17.4", + "lodash-es": "4.17.4", + "loose-envify": "1.3.1", + "symbol-observable": "1.0.4" + } + }, + "redux-form": { + "version": "6.6.3", + "resolved": "https://registry.npmjs.org/redux-form/-/redux-form-6.6.3.tgz", + "integrity": "sha1-YjYmVPIhTIOo+fy4MTcCu0b5IgU=", + "requires": { + "deep-equal": "1.0.1", + "es6-error": "4.0.2", + "hoist-non-react-statics": "1.2.0", + "invariant": "2.2.2", + "is-promise": "2.1.0", + "lodash": "4.17.4", + "lodash-es": "4.17.4", + "prop-types": "15.5.10" + } + }, + "redux-saga": { + "version": "0.15.6", + "resolved": "https://registry.npmjs.org/redux-saga/-/redux-saga-0.15.6.tgz", + "integrity": "sha1-hjjcUi3mxsCklv6LK1RmKHrC3E0=" + }, + "regenerate": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.3.2.tgz", + "integrity": "sha1-0ZQcZ7rUN+G+dkM63Vs4X5WxkmA=", + "dev": true + }, + "regenerator-runtime": { + "version": "0.9.6", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.9.6.tgz", + "integrity": "sha1-0z65XQ0gAaS+OWWXB8UbDLcc4Ck=" + }, + "regenerator-transform": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.10.1.tgz", + "integrity": "sha512-PJepbvDbuK1xgIgnau7Y90cwaAmO/LCLMI2mPvaXq2heGMR3aWW5/BQvYrhJ8jgmQjXewXvBjzfqKcVOmhjZ6Q==", + "dev": true, + "requires": { + "babel-runtime": "6.18.0", + "babel-types": "6.26.0", + "private": "0.1.7" + } + }, + "regex-cache": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.3.tgz", + "integrity": "sha1-mxpsNdTQ3871cRrmUejp09cRQUU=", + "dev": true, + "requires": { + "is-equal-shallow": "0.1.3", + "is-primitive": "2.0.0" + } + }, + "regexpu-core": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-2.0.0.tgz", + "integrity": "sha1-SdA4g3uNz4v6W5pCE5k45uoq4kA=", + "dev": true, + "requires": { + "regenerate": "1.3.2", + "regjsgen": "0.2.0", + "regjsparser": "0.1.5" + } + }, + "regjsgen": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.2.0.tgz", + "integrity": "sha1-bAFq3qxVT3WCP+N6wFuS1aTtsfc=", + "dev": true + }, + "regjsparser": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.1.5.tgz", + "integrity": "sha1-fuj4Tcb6eS0/0K4ijSS9lJ6tIFw=", + "dev": true, + "requires": { + "jsesc": "0.5.0" + }, + "dependencies": { + "jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=", + "dev": true + } + } + }, + "remedial": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/remedial/-/remedial-1.0.7.tgz", + "integrity": "sha1-1mdEE6ZWdgB74A3UAJgJh7LDAME=", + "dev": true + }, + "remove-trailing-separator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", + "dev": true + }, + "repeat-element": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.2.tgz", + "integrity": "sha1-7wiaF40Ug7quTZPrmLT55OEdmQo=", + "dev": true + }, + "repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", + "dev": true + }, + "repeating": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", + "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", + "dev": true, + "requires": { + "is-finite": "1.0.2" + } + }, + "require-uncached": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/require-uncached/-/require-uncached-1.0.3.tgz", + "integrity": "sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM=", + "dev": true, + "requires": { + "caller-path": "0.1.0", + "resolve-from": "1.0.1" + } + }, + "requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=", + "dev": true + }, + "reselect": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-3.0.1.tgz", + "integrity": "sha1-79qpjqdFEyTQkrKyFjpqHXqaIUc=" + }, + "resolve": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.4.0.tgz", + "integrity": "sha512-aW7sVKPufyHqOmyyLzg/J+8606v5nevBgaliIlV7nUpVMsDnoBGV/cbSLNjZAg9q0Cfd/+easKVKQ8vOu8fn1Q==", + "dev": true, + "requires": { + "path-parse": "1.0.5" + } + }, + "resolve-from": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-1.0.1.tgz", + "integrity": "sha1-Jsv+k10a7uq7Kbw/5a6wHpPUQiY=", + "dev": true + }, + "resolve-pathname": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-2.1.0.tgz", + "integrity": "sha1-6DWIAbhrg7F1YNTjw4LXrvIQCUQ=" + }, + "restore-cursor": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-1.0.1.tgz", + "integrity": "sha1-NGYfRohjJ/7SmRR5FSJS35LapUE=", + "dev": true, + "requires": { + "exit-hook": "1.1.1", + "onetime": "1.1.0" + } + }, + "right-align": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz", + "integrity": "sha1-YTObci/mo1FWiSENJOFMlhSGE+8=", + "dev": true, + "requires": { + "align-text": "0.1.4" + } + }, + "rimraf": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.1.tgz", + "integrity": "sha1-wjOOxkPfeht/5cVPqG9XQopV8z0=", + "dev": true, + "requires": { + "glob": "7.1.2" + } + }, + "ripemd160": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-0.2.0.tgz", + "integrity": "sha1-K/GYveFnys+lHAqSjoS2i74XH84=", + "dev": true + }, + "run-async": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-0.1.0.tgz", + "integrity": "sha1-yK1KXhEGYeQCp9IbUw4AnyX444k=", + "dev": true, + "requires": { + "once": "1.4.0" + } + }, + "rx-lite": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/rx-lite/-/rx-lite-3.1.2.tgz", + "integrity": "sha1-Gc5QLKVyZl87ZHsQk5+X/RYV8QI=", + "dev": true + }, + "safe-buffer": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", + "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==", + "dev": true + }, + "samsam": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/samsam/-/samsam-1.2.1.tgz", + "integrity": "sha1-7dOQk6MYQ3DLhZJDsr3yVefY6mc=", + "dev": true + }, + "sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", + "dev": true + }, + "selenium-webdriver": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-3.3.0.tgz", + "integrity": "sha1-8U2bBM7pSV1ChNIhBbGJuDBczKE=", + "dev": true, + "requires": { + "adm-zip": "0.4.7", + "rimraf": "2.6.1", + "tmp": "0.0.30", + "xml2js": "0.4.19" + }, + "dependencies": { + "tmp": { + "version": "0.0.30", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.30.tgz", + "integrity": "sha1-ckGdSovn1s51FI/YsyTlk6cRwu0=", + "dev": true, + "requires": { + "os-tmpdir": "1.0.2" + } + } + } + }, + "semver": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/semver/-/semver-4.3.6.tgz", + "integrity": "sha1-MAvG4OhjdPe6YQaLWx7NV/xlMto=", + "dev": true + }, + "send": { + "version": "0.15.4", + "resolved": "https://registry.npmjs.org/send/-/send-0.15.4.tgz", + "integrity": "sha1-mF+qPihLAnPHkzZKNcZze9k5Bbk=", + "dev": true, + "requires": { + "debug": "2.6.8", + "depd": "1.1.1", + "destroy": "1.0.4", + "encodeurl": "1.0.1", + "escape-html": "1.0.3", + "etag": "1.8.0", + "fresh": "0.5.0", + "http-errors": "1.6.2", + "mime": "1.3.4", + "ms": "2.0.0", + "on-finished": "2.3.0", + "range-parser": "1.2.0", + "statuses": "1.3.1" + } + }, + "serve-index": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.0.tgz", + "integrity": "sha1-0rKA/FYNYW7oG0i/D6gqvtJIXOc=", + "dev": true, + "requires": { + "accepts": "1.3.3", + "batch": "0.6.1", + "debug": "2.6.8", + "escape-html": "1.0.3", + "http-errors": "1.6.2", + "mime-types": "2.1.16", + "parseurl": "1.3.1" + } + }, + "serve-static": { + "version": "1.12.4", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.12.4.tgz", + "integrity": "sha1-m2qpjutyU8Tu3Ewfb9vKYJkBqWE=", + "dev": true, + "requires": { + "encodeurl": "1.0.1", + "escape-html": "1.0.3", + "parseurl": "1.3.1", + "send": "0.15.4" + } + }, + "set-immediate-shim": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz", + "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=", + "dev": true + }, + "setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=" + }, + "setprototypeof": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz", + "integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ=", + "dev": true + }, + "sha.js": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.2.6.tgz", + "integrity": "sha1-F93t3F9yL7ZlAWWIlUYZd4ZzFbo=", + "dev": true + }, + "shelljs": { + "version": "0.7.8", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.7.8.tgz", + "integrity": "sha1-3svPh0sNHl+3LhSxZKloMEjprLM=", + "dev": true, + "requires": { + "glob": "7.1.2", + "interpret": "1.0.3", + "rechoir": "0.6.2" + } + }, + "simple-assign": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/simple-assign/-/simple-assign-0.1.0.tgz", + "integrity": "sha1-F/0wZqXz13OPUDIbsPFMooHMS6o=" + }, + "sinon": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-2.1.0.tgz", + "integrity": "sha1-4Fep0r8bMvXW3WJijKnuOWGwyvs=", + "dev": true, + "requires": { + "diff": "3.3.0", + "formatio": "1.2.0", + "lolex": "1.6.0", + "native-promise-only": "0.8.1", + "path-to-regexp": "1.7.0", + "samsam": "1.2.1", + "text-encoding": "0.6.4", + "type-detect": "4.0.3" + }, + "dependencies": { + "diff": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.3.0.tgz", + "integrity": "sha512-w0XZubFWn0Adlsapj9EAWX0FqWdO4tz8kc3RiYdWLh4k/V8PTb6i0SMgXt0vRM3zyKnT8tKO7mUlieRQHIjMNg==", + "dev": true + } + } + }, + "slash": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", + "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=", + "dev": true + }, + "slice-ansi": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-0.0.4.tgz", + "integrity": "sha1-7b+JA/ZvfOL46v1s7tZeJkyDGzU=", + "dev": true + }, + "sockjs": { + "version": "0.3.18", + "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.18.tgz", + "integrity": "sha1-2bKJMWyn33dZXvKZ4HXw+TfrQgc=", + "dev": true, + "requires": { + "faye-websocket": "0.10.0", + "uuid": "2.0.3" + } + }, + "sockjs-client": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/sockjs-client/-/sockjs-client-1.1.4.tgz", + "integrity": "sha1-W6vjhrd15M8U51IJEUUmVAFsixI=", + "dev": true, + "requires": { + "debug": "2.6.8", + "eventsource": "0.1.6", + "faye-websocket": "0.11.1", + "inherits": "2.0.3", + "json3": "3.3.2", + "url-parse": "1.1.9" + }, + "dependencies": { + "faye-websocket": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.1.tgz", + "integrity": "sha1-8O/hjE9W5PQK/H4Gxxn9XuYYjzg=", + "dev": true, + "requires": { + "websocket-driver": "0.6.5" + } + } + } + }, + "sort-keys": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz", + "integrity": "sha1-RBttTTRnmPG05J6JIK37oOVD+a0=", + "dev": true, + "requires": { + "is-plain-obj": "1.1.0" + } + }, + "source-list-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.0.tgz", + "integrity": "sha512-I2UmuJSRr/T8jisiROLU3A3ltr+swpniSmNPI4Ml3ZCX6tVnDsuZzK7F2hl5jTqbZBWCEKlj5HRQiPExXLgE8A==", + "dev": true + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "source-map-support": { + "version": "0.4.16", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.16.tgz", + "integrity": "sha512-A6vlydY7H/ljr4L2UOhDSajQdZQ6dMD7cLH0pzwcmwLyc9u8PNI4WGtnfDDzX7uzGL6c/T+ORL97Zlh+S4iOrg==", + "dev": true, + "requires": { + "source-map": "0.5.7" + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "statuses": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", + "integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4=", + "dev": true + }, + "stream-browserify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-1.0.0.tgz", + "integrity": "sha1-v5tKv7QrJ011FHnkTg/yZWtvEZM=", + "dev": true, + "requires": { + "inherits": "2.0.3", + "readable-stream": "1.1.14" + }, + "dependencies": { + "readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "dev": true, + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "0.0.1", + "string_decoder": "0.10.31" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + } + } + }, + "stream-cache": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stream-cache/-/stream-cache-0.0.2.tgz", + "integrity": "sha1-GsWtaDJCjKVWZ9ve45Xa1ObbEY8=", + "dev": true + }, + "strict-uri-encode": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz", + "integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=" + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "requires": { + "code-point-at": "1.1.0", + "is-fullwidth-code-point": "1.0.0", + "strip-ansi": "3.0.1" + } + }, + "string.prototype.trim": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.1.2.tgz", + "integrity": "sha1-0E3iyJ4Tf019IG8Ia17S+ua+jOo=", + "requires": { + "define-properties": "1.1.2", + "es-abstract": "1.8.0", + "function-bind": "1.1.0" + } + }, + "string_decoder": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", + "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", + "dev": true, + "requires": { + "safe-buffer": "5.1.1" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "2.1.1" + } + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true + }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "dev": true + }, + "style-loader": { + "version": "0.16.1", + "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-0.16.1.tgz", + "integrity": "sha1-UOMlJY1OeEId2WgGNrQehmFZXRA=", + "dev": true, + "requires": { + "loader-utils": "1.1.0" + }, + "dependencies": { + "loader-utils": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.1.0.tgz", + "integrity": "sha1-yYrvSIvM7aL/teLeZG1qdUQp9c0=", + "dev": true, + "requires": { + "big.js": "3.1.3", + "emojis-list": "2.1.0", + "json5": "0.5.1" + } + } + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + }, + "svgo": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-0.7.2.tgz", + "integrity": "sha1-n1dyQTlSE1xv779Ar+ak+qiLS7U=", + "dev": true, + "requires": { + "coa": "1.0.4", + "colors": "1.1.2", + "csso": "2.3.2", + "js-yaml": "3.7.0", + "mkdirp": "0.5.1", + "sax": "1.2.4", + "whet.extend": "0.9.9" + } + }, + "symbol-observable": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.0.4.tgz", + "integrity": "sha1-Kb9hXUqnEhvdiYsi1LP5vE4qoD0=" + }, + "table": { + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/table/-/table-3.8.3.tgz", + "integrity": "sha1-K7xULw/amGGnVdOUf+/Ys/UThV8=", + "dev": true, + "requires": { + "ajv": "4.11.8", + "ajv-keywords": "1.5.1", + "chalk": "1.1.3", + "lodash": "4.17.4", + "slice-ansi": "0.0.4", + "string-width": "2.1.1" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "2.0.0", + "strip-ansi": "4.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "3.0.0" + } + } + } + }, + "tapable": { + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-0.1.10.tgz", + "integrity": "sha1-KcNXB8K3DlDQdIK10gLo7URtr9Q=", + "dev": true + }, + "text-encoding": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/text-encoding/-/text-encoding-0.6.4.tgz", + "integrity": "sha1-45mpgiV6J22uQou5KEXLcb3CbRk=", + "dev": true + }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "dev": true + }, + "time-stamp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/time-stamp/-/time-stamp-2.0.0.tgz", + "integrity": "sha1-lcakRTDhW6jW9KPsuMOj+sRto1c=", + "dev": true + }, + "timers-browserify": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-1.4.2.tgz", + "integrity": "sha1-ycWLV1voQHN1y14kYtrO50NZ9B0=", + "dev": true, + "requires": { + "process": "0.11.10" + } + }, + "to-fast-properties": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", + "integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=", + "dev": true + }, + "trim-right": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", + "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=", + "dev": true + }, + "tryit": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tryit/-/tryit-1.0.3.tgz", + "integrity": "sha1-OTvnMKlEb9Hq1tpZoBQwjzbCics=", + "dev": true + }, + "tty-browserify": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz", + "integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=", + "dev": true + }, + "type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "dev": true, + "requires": { + "prelude-ls": "1.1.2" + } + }, + "type-detect": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.3.tgz", + "integrity": "sha1-Dj8mcLRAmbC0bChNE2p+9Jx0wuo=", + "dev": true + }, + "type-is": { + "version": "1.6.15", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.15.tgz", + "integrity": "sha1-yrEPtJCeRByChC6v4a1kbIGARBA=", + "dev": true, + "requires": { + "media-typer": "0.3.0", + "mime-types": "2.1.16" + } + }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", + "dev": true + }, + "ua-parser-js": { + "version": "0.7.14", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.14.tgz", + "integrity": "sha1-EQ1T+kw/MmwSEpK76skE0uAzh8o=" + }, + "uglify-js": { + "version": "2.7.5", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.7.5.tgz", + "integrity": "sha1-RhLAx7qu4rp8SH3kkErhIgefLKg=", + "dev": true, + "requires": { + "async": "0.2.10", + "source-map": "0.5.7", + "uglify-to-browserify": "1.0.2", + "yargs": "3.10.0" + }, + "dependencies": { + "async": { + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/async/-/async-0.2.10.tgz", + "integrity": "sha1-trvgsGdLnXGXCMo43owjfLUmw9E=", + "dev": true + }, + "camelcase": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", + "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=", + "dev": true + }, + "cliui": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz", + "integrity": "sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=", + "dev": true, + "requires": { + "center-align": "0.1.3", + "right-align": "0.1.3", + "wordwrap": "0.0.2" + } + }, + "wordwrap": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", + "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=", + "dev": true + }, + "yargs": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", + "integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=", + "dev": true, + "requires": { + "camelcase": "1.2.1", + "cliui": "2.1.0", + "decamelize": "1.2.0", + "window-size": "0.1.0" + } + } + } + }, + "uglify-to-browserify": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz", + "integrity": "sha1-bgkk1r2mta/jSeOabWMoUKD4grc=", + "dev": true + }, + "uid-number": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/uid-number/-/uid-number-0.0.5.tgz", + "integrity": "sha1-Wj2yPvXb1VuB/ODsmirG/M3ruB4=", + "dev": true + }, + "uniq": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/uniq/-/uniq-1.0.1.tgz", + "integrity": "sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8=", + "dev": true + }, + "uniqid": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/uniqid/-/uniqid-4.1.1.tgz", + "integrity": "sha1-iSIN32t1GuUrX3JISGNShZa7hME=", + "dev": true, + "requires": { + "macaddress": "0.2.8" + } + }, + "uniqs": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/uniqs/-/uniqs-2.0.0.tgz", + "integrity": "sha1-/+3ks2slKQaW5uFl1KWe25mOawI=", + "dev": true + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", + "dev": true + }, + "url": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/url/-/url-0.10.3.tgz", + "integrity": "sha1-Ah5NnHcF8hu/N9A861h2dAJ3TGQ=", + "dev": true, + "requires": { + "punycode": "1.3.2", + "querystring": "0.2.0" + }, + "dependencies": { + "punycode": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", + "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=", + "dev": true + } + } + }, + "url-parse": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.1.9.tgz", + "integrity": "sha1-xn8dd11R8KGJEd17P/rSe7nlvRk=", + "dev": true, + "requires": { + "querystringify": "1.0.0", + "requires-port": "1.0.0" + }, + "dependencies": { + "querystringify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-1.0.0.tgz", + "integrity": "sha1-YoYkIRLFtxL6ZU5SZlK/ahP/Bcs=", + "dev": true + } + } + }, + "user-home": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/user-home/-/user-home-1.1.1.tgz", + "integrity": "sha1-K1viOjK2Onyd640PKNSFcko98ZA=", + "dev": true + }, + "util": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", + "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", + "dev": true, + "requires": { + "inherits": "2.0.1" + }, + "dependencies": { + "inherits": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", + "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=", + "dev": true + } + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, + "utils-merge": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.0.tgz", + "integrity": "sha1-ApT7kiu5N1FTVBxPcJYjHyh8ivg=", + "dev": true + }, + "uuid": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-2.0.3.tgz", + "integrity": "sha1-Z+LoY3lyFVMN/zGOW/nc6/1Hsho=", + "dev": true + }, + "v8flags": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-2.1.1.tgz", + "integrity": "sha1-qrGh+jDUX4jdMhFIh1rALAtV5bQ=", + "dev": true, + "requires": { + "user-home": "1.1.1" + } + }, + "value-equal": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/value-equal/-/value-equal-0.2.1.tgz", + "integrity": "sha1-wiCjBDYfzmmU277ao8fhobiVhx0=" + }, + "vary": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.1.tgz", + "integrity": "sha1-Z1Neu2lMHVIldFeYRmUyP1h+jTc=", + "dev": true + }, + "vendors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/vendors/-/vendors-1.0.1.tgz", + "integrity": "sha1-N61zyO5Bf7PVgOeFMSMH0nSEfyI=", + "dev": true + }, + "vm-browserify": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-0.0.4.tgz", + "integrity": "sha1-XX6kW7755Kb/ZflUOOCofDV9WnM=", + "dev": true, + "requires": { + "indexof": "0.0.1" + } + }, + "warning": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/warning/-/warning-3.0.0.tgz", + "integrity": "sha1-MuU3fLVy3kqwR1O9+IIcAe1gW3w=", + "requires": { + "loose-envify": "1.3.1" + } + }, + "watchpack": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-0.2.9.tgz", + "integrity": "sha1-Yuqkq15bo1/fwBgnVibjwPXj+ws=", + "dev": true, + "requires": { + "async": "0.9.2", + "chokidar": "1.7.0", + "graceful-fs": "4.1.11" + }, + "dependencies": { + "async": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/async/-/async-0.9.2.tgz", + "integrity": "sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0=", + "dev": true + } + } + }, + "webpack": { + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-1.13.3.tgz", + "integrity": "sha1-55xG/lo3xcpwCEugiUxZXNy0KBU=", + "dev": true, + "requires": { + "acorn": "3.3.0", + "async": "1.5.2", + "clone": "1.0.2", + "enhanced-resolve": "0.9.1", + "interpret": "0.6.6", + "loader-utils": "0.2.17", + "memory-fs": "0.3.0", + "mkdirp": "0.5.1", + "node-libs-browser": "0.6.0", + "optimist": "0.6.1", + "supports-color": "3.2.3", + "tapable": "0.1.10", + "uglify-js": "2.7.5", + "watchpack": "0.2.9", + "webpack-core": "0.6.9" + }, + "dependencies": { + "acorn": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz", + "integrity": "sha1-ReN/s56No/JbruP/U2niu18iAXo=", + "dev": true + }, + "interpret": { + "version": "0.6.6", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-0.6.6.tgz", + "integrity": "sha1-/s16GOfOXKar+5U+H4YhOknxYls=", + "dev": true + }, + "supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "dev": true, + "requires": { + "has-flag": "1.0.0" + } + } + } + }, + "webpack-core": { + "version": "0.6.9", + "resolved": "https://registry.npmjs.org/webpack-core/-/webpack-core-0.6.9.tgz", + "integrity": "sha1-/FcViMhVjad76e+23r3Fo7FyvcI=", + "dev": true, + "requires": { + "source-list-map": "0.1.8", + "source-map": "0.4.4" + }, + "dependencies": { + "source-list-map": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-0.1.8.tgz", + "integrity": "sha1-xVCyq1Qn9rPyH1r+rYjE9Vh7IQY=", + "dev": true + }, + "source-map": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", + "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=", + "dev": true, + "requires": { + "amdefine": "1.0.1" + } + } + } + }, + "webpack-dev-middleware": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-1.12.0.tgz", + "integrity": "sha1-007++y7dp+HTtdvgcolRMhllFwk=", + "dev": true, + "requires": { + "memory-fs": "0.4.1", + "mime": "1.3.4", + "path-is-absolute": "1.0.1", + "range-parser": "1.2.0", + "time-stamp": "2.0.0" + }, + "dependencies": { + "memory-fs": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz", + "integrity": "sha1-OpoguEYlI+RHz7x+i7gO1me/xVI=", + "dev": true, + "requires": { + "errno": "0.1.4", + "readable-stream": "2.3.3" + } + } + } + }, + "webpack-dev-server": { + "version": "1.16.5", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-1.16.5.tgz", + "integrity": "sha1-DL1fLSrI1OWTqs1clwLnu9XlmJI=", + "dev": true, + "requires": { + "compression": "1.7.0", + "connect-history-api-fallback": "1.3.0", + "express": "4.15.4", + "http-proxy-middleware": "0.17.4", + "open": "0.0.5", + "optimist": "0.6.1", + "serve-index": "1.9.0", + "sockjs": "0.3.18", + "sockjs-client": "1.1.4", + "stream-cache": "0.0.2", + "strip-ansi": "3.0.1", + "supports-color": "3.2.3", + "webpack-dev-middleware": "1.12.0" + }, + "dependencies": { + "supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "dev": true, + "requires": { + "has-flag": "1.0.0" + } + } + } + }, + "webpack-sources": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-0.1.5.tgz", + "integrity": "sha1-qh86vw8NdNtxEcQOUAuE+WZkB1A=", + "dev": true, + "requires": { + "source-list-map": "0.1.8", + "source-map": "0.5.7" + }, + "dependencies": { + "source-list-map": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-0.1.8.tgz", + "integrity": "sha1-xVCyq1Qn9rPyH1r+rYjE9Vh7IQY=", + "dev": true + } + } + }, + "websocket-driver": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.6.5.tgz", + "integrity": "sha1-XLJVbOuF9Dc8bYI4qmkchFThOjY=", + "dev": true, + "requires": { + "websocket-extensions": "0.1.1" + } + }, + "websocket-extensions": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.1.tgz", + "integrity": "sha1-domUmcGEtu91Q3fC27DNbLVdKec=", + "dev": true + }, + "whatwg-fetch": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.3.tgz", + "integrity": "sha1-nITsLc9oGH/wC8ZOEnS0QhduHIQ=" + }, + "whet.extend": { + "version": "0.9.9", + "resolved": "https://registry.npmjs.org/whet.extend/-/whet.extend-0.9.9.tgz", + "integrity": "sha1-+HfVv2SMl+WqVC+twW1qJZucEaE=", + "dev": true + }, + "window-size": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz", + "integrity": "sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0=", + "dev": true + }, + "wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", + "dev": true + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "write": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/write/-/write-0.2.1.tgz", + "integrity": "sha1-X8A4KOJkzqP+kUVUdvejxWbLB1c=", + "dev": true, + "requires": { + "mkdirp": "0.5.1" + } + }, + "xml2js": { + "version": "0.4.19", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz", + "integrity": "sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==", + "dev": true, + "requires": { + "sax": "1.2.4", + "xmlbuilder": "9.0.4" + } + }, + "xmlbuilder": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.4.tgz", + "integrity": "sha1-UZy0ymhtAFqEINNJbz8MruzKWA8=", + "dev": true + }, + "xtend": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", + "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=", + "dev": true + } + } +} diff --git a/package.json b/package.json index c73c9353..04e7bcd5 100644 --- a/package.json +++ b/package.json @@ -36,9 +36,10 @@ "css-loader": "~0.28.0", "enzyme": "~2.8.2", "eslint": "~3.19.0", - "eslint-config-airbnb": "~14.1.0", + "eslint-config-prettier": "^2.3.0", "eslint-plugin-import": "~2.2.0", "eslint-plugin-jsx-a11y": "~4.0.0", + "eslint-plugin-prettier": "^2.1.2", "eslint-plugin-react": "~6.10.3", "express": "~4.15.2", "extract-text-webpack-plugin": "~1.0.1", @@ -46,6 +47,7 @@ "full-icu": "~1.1.3", "ignore-styles": "~5.0.1", "mocha": "~3.2.0", + "prettier": "^1.5.2", "react-addons-test-utils": "~15.5.1", "selenium-webdriver": "~3.3.0", "sinon": "~2.1.0", diff --git a/src/Admin.js b/src/Admin.js index 1edb1b46..2146a20a 100644 --- a/src/Admin.js +++ b/src/Admin.js @@ -4,7 +4,11 @@ import { combineReducers, createStore, compose, applyMiddleware } from 'redux'; import { Provider } from 'react-redux'; import createHistory from 'history/createHashHistory'; import { Switch, Route } from 'react-router-dom'; -import { ConnectedRouter, routerReducer, routerMiddleware } from 'react-router-redux'; +import { + ConnectedRouter, + routerReducer, + routerMiddleware, +} from 'react-router-redux'; import { reducer as formReducer } from 'redux-form'; import createSagaMiddleware from 'redux-saga'; import { all, fork } from 'redux-saga/effects'; @@ -46,19 +50,21 @@ const Admin = ({ routing: routerReducer, ...customReducers, }); - const resettableAppReducer = (state, action) => appReducer(action.type !== USER_LOGOUT ? state : undefined, action); + const resettableAppReducer = (state, action) => + appReducer(action.type !== USER_LOGOUT ? state : undefined, action); const saga = function* rootSaga() { - yield all([ - crudSaga(restClient, authClient), - ...customSagas, - ].map(fork)); + yield all([crudSaga(restClient, authClient), ...customSagas].map(fork)); }; const sagaMiddleware = createSagaMiddleware(); const routerHistory = history || createHistory(); - const store = createStore(resettableAppReducer, initialState, compose( - applyMiddleware(sagaMiddleware, routerMiddleware(routerHistory)), - window.devToolsExtension ? window.devToolsExtension() : f => f, - )); + const store = createStore( + resettableAppReducer, + initialState, + compose( + applyMiddleware(sagaMiddleware, routerMiddleware(routerHistory)), + window.devToolsExtension ? window.devToolsExtension() : f => f + ) + ); sagaMiddleware.run(saga); const logout = authClient ? createElement(logoutButton || Logout) : null; @@ -69,23 +75,32 @@ const Admin = ({
- createElement(loginPage || Login, { - location, - title, - theme, - })} /> - createElement(appLayout || DefaultLayout, { - dashboard, - customRoutes, - menu: createElement(menu || Menu, { - logout, - resources, - hasDashboard: !!dashboard, - }), - resources, - title, - theme, - })} /> + + createElement(loginPage || Login, { + location, + title, + theme, + })} + /> + + createElement(appLayout || DefaultLayout, { + dashboard, + customRoutes, + menu: createElement(menu || Menu, { + logout, + resources, + hasDashboard: !!dashboard, + }), + resources, + title, + theme, + })} + />
@@ -94,7 +109,10 @@ const Admin = ({ ); }; -const componentPropType = PropTypes.oneOfType([PropTypes.func, PropTypes.string]); +const componentPropType = PropTypes.oneOfType([ + PropTypes.func, + PropTypes.string, +]); Admin.propTypes = { appLayout: componentPropType, diff --git a/src/AdminRoutes.js b/src/AdminRoutes.js index 67220ded..0e2ab232 100644 --- a/src/AdminRoutes.js +++ b/src/AdminRoutes.js @@ -4,46 +4,53 @@ import { Redirect, Route, Switch } from 'react-router-dom'; import CrudRoute from './CrudRoute'; import Restricted from './auth/Restricted'; -const AdminRoutes = ({ customRoutes, resources = [], dashboard }) => ( +const AdminRoutes = ({ customRoutes, resources = [], dashboard }) => - {customRoutes && customRoutes.map((route, index) => - ) - } + {customRoutes && + customRoutes.map((route, index) => + + )} {resources.map(resource => } + render={() => + } /> )} {dashboard ? - - {React.createElement(dashboard)} - } - /> - : (resources[0] && } /> - ) - } - -); + exact + path="/" + render={routeProps => + + {React.createElement(dashboard)} + } + /> + : resources[0] && + } + />} + ; export default AdminRoutes; diff --git a/src/AdminRoutes.spec.js b/src/AdminRoutes.spec.js index c0caba9f..a6f93723 100644 --- a/src/AdminRoutes.spec.js +++ b/src/AdminRoutes.spec.js @@ -18,12 +18,19 @@ describe('', () => { // the Provider is required because the dashboard is wrapped by , which is a connected component const store = createStore(x => x); const resources = [ - { name: 'posts', list: PostList, edit: PostEdit, create: PostCreate, show: PostShow, remove: PostDelete }, + { + name: 'posts', + list: PostList, + edit: PostEdit, + create: PostCreate, + show: PostShow, + remove: PostDelete, + }, ]; it('should show dashboard on / when provided', () => { const wrapper = render( - + @@ -33,7 +40,7 @@ describe('', () => { it('should show resource list on /[resourcename]', () => { const wrapper = render( - + @@ -43,7 +50,7 @@ describe('', () => { it('should show resource edit on /[resourcename]/:id', () => { const wrapper = render( - + @@ -53,7 +60,7 @@ describe('', () => { it('should show resource show on /[resourcename]/:id/show', () => { const wrapper = render( - + @@ -63,7 +70,7 @@ describe('', () => { it('should show resource delete on /[resourcename]/:id/delete', () => { const wrapper = render( - + @@ -71,13 +78,14 @@ describe('', () => { assert.equal(wrapper.html(), '
PostDelete
'); }); it('should accept custom routes', () => { - const customRoutes = [ - , - ]; + const customRoutes = []; const wrapper = render( - - + + ); diff --git a/src/CrudRoute.js b/src/CrudRoute.js index 083c5815..2e057b22 100644 --- a/src/CrudRoute.js +++ b/src/CrudRoute.js @@ -19,11 +19,36 @@ const CrudRoute = ({ resource, list, create, edit, show, remove, options }) => {
; return ( - {list && } - {create && } - {edit && } - {show && } - {remove && } + {list && + } + {create && + } + {edit && + } + {show && + } + {remove && + } ); }; diff --git a/src/Resource.js b/src/Resource.js index fb47e315..733c0cf4 100644 --- a/src/Resource.js +++ b/src/Resource.js @@ -2,9 +2,16 @@ import React from 'react'; import PropTypes from 'prop-types'; import ViewListIcon from 'material-ui/svg-icons/action/view-list'; -const componentPropType = PropTypes.oneOfType([PropTypes.func, PropTypes.string]); +const componentPropType = PropTypes.oneOfType([ + PropTypes.func, + PropTypes.string, +]); -const Resource = () => <Resource> elements are for configuration only and should not be rendered; +const Resource = () => + + <Resource> elements are for configuration only and should not be + rendered + ; Resource.propTypes = { name: PropTypes.string.isRequired, diff --git a/src/actions/dataActions.js b/src/actions/dataActions.js index 95b2542d..c872816c 100644 --- a/src/actions/dataActions.js +++ b/src/actions/dataActions.js @@ -13,7 +13,13 @@ export const CRUD_GET_LIST_LOADING = 'AOR/CRUD_GET_LIST_LOADING'; export const CRUD_GET_LIST_FAILURE = 'AOR/CRUD_GET_LIST_FAILURE'; export const CRUD_GET_LIST_SUCCESS = 'AOR/CRUD_GET_LIST_SUCCESS'; -export const crudGetList = (resource, pagination, sort, filter, cancelPrevious = true) => ({ +export const crudGetList = ( + resource, + pagination, + sort, + filter, + cancelPrevious = true +) => ({ type: CRUD_GET_LIST, payload: { pagination, sort, filter }, meta: { resource, fetch: GET_LIST, cancelPrevious }, @@ -46,7 +52,14 @@ export const CRUD_UPDATE_LOADING = 'AOR/CRUD_UPDATE_LOADING'; export const CRUD_UPDATE_FAILURE = 'AOR/CRUD_UPDATE_FAILURE'; export const CRUD_UPDATE_SUCCESS = 'AOR/CRUD_UPDATE_SUCCESS'; -export const crudUpdate = (resource, id, data, previousData, basePath, redirectTo = 'show') => ({ +export const crudUpdate = ( + resource, + id, + data, + previousData, + basePath, + redirectTo = 'show' +) => ({ type: CRUD_UPDATE, payload: { id, data, previousData, basePath, redirectTo }, meta: { resource, fetch: UPDATE, cancelPrevious: false }, @@ -81,19 +94,46 @@ export const CRUD_GET_MATCHING_LOADING = 'AOR/CRUD_GET_MATCHING_LOADING'; export const CRUD_GET_MATCHING_FAILURE = 'AOR/CRUD_GET_MATCHING_FAILURE'; export const CRUD_GET_MATCHING_SUCCESS = 'AOR/CRUD_GET_MATCHING_SUCCESS'; -export const crudGetMatching = (reference, relatedTo, pagination, sort, filter) => ({ +export const crudGetMatching = ( + reference, + relatedTo, + pagination, + sort, + filter +) => ({ type: CRUD_GET_MATCHING, payload: { pagination, sort, filter }, - meta: { resource: reference, relatedTo, fetch: GET_LIST, cancelPrevious: false }, + meta: { + resource: reference, + relatedTo, + fetch: GET_LIST, + cancelPrevious: false, + }, }); export const CRUD_GET_MANY_REFERENCE = 'AOR/CRUD_GET_MANY_REFERENCE'; -export const CRUD_GET_MANY_REFERENCE_LOADING = 'AOR/CRUD_GET_MANY_REFERENCE_LOADING'; -export const CRUD_GET_MANY_REFERENCE_FAILURE = 'AOR/CRUD_GET_MANY_REFERENCE_FAILURE'; -export const CRUD_GET_MANY_REFERENCE_SUCCESS = 'AOR/CRUD_GET_MANY_REFERENCE_SUCCESS'; - -export const crudGetManyReference = (reference, target, id, relatedTo, pagination, sort, filter) => ({ +export const CRUD_GET_MANY_REFERENCE_LOADING = + 'AOR/CRUD_GET_MANY_REFERENCE_LOADING'; +export const CRUD_GET_MANY_REFERENCE_FAILURE = + 'AOR/CRUD_GET_MANY_REFERENCE_FAILURE'; +export const CRUD_GET_MANY_REFERENCE_SUCCESS = + 'AOR/CRUD_GET_MANY_REFERENCE_SUCCESS'; + +export const crudGetManyReference = ( + reference, + target, + id, + relatedTo, + pagination, + sort, + filter +) => ({ type: CRUD_GET_MANY_REFERENCE, payload: { target, id, pagination, sort, filter }, - meta: { resource: reference, relatedTo, fetch: GET_MANY_REFERENCE, cancelPrevious: false }, + meta: { + resource: reference, + relatedTo, + fetch: GET_MANY_REFERENCE, + cancelPrevious: false, + }, }); diff --git a/src/auth/Restricted.js b/src/auth/Restricted.js index db5d370f..e06ed882 100644 --- a/src/auth/Restricted.js +++ b/src/auth/Restricted.js @@ -23,7 +23,7 @@ export class Restricted extends Component { authParams: PropTypes.object, location: PropTypes.object, userCheck: PropTypes.func, - } + }; componentWillMount() { this.checkAuthentication(this.props); diff --git a/src/auth/Restricted.spec.js b/src/auth/Restricted.spec.js index e9a3777b..9c382389 100644 --- a/src/auth/Restricted.spec.js +++ b/src/auth/Restricted.spec.js @@ -9,18 +9,32 @@ describe('', () => { const Foo = () =>
Foo
; it('should call userCheck on mount', () => { const userCheck = sinon.spy(); - shallow(, { lifecycleExperimental: true }); + shallow( + + + , + { lifecycleExperimental: true } + ); assert(userCheck.calledOnce); }); it('should call userCheck on update', () => { const userCheck = sinon.spy(); - const wrapper = shallow(, { lifecycleExperimental: true }); + const wrapper = shallow( + + + , + { lifecycleExperimental: true } + ); wrapper.setProps({ location: { pathname: 'foo' }, userCheck }); assert(userCheck.calledTwice); }); it('should render its child by default', () => { const userCheck = sinon.stub(); - const wrapper = render(); + const wrapper = render( + + + + ); assert.equal(wrapper.html(), '
Foo
'); }); }); diff --git a/src/i18n/TranslationProvider.js b/src/i18n/TranslationProvider.js index 783d7bf2..369cc0c2 100644 --- a/src/i18n/TranslationProvider.js +++ b/src/i18n/TranslationProvider.js @@ -6,21 +6,24 @@ import { compose, withContext } from 'recompose'; import defaultMessages from './messages'; -const withI18nContext = withContext({ - translate: PropTypes.func.isRequired, - locale: PropTypes.string.isRequired, -}, ({ locale, messages = {} }) => { - const userMessages = messages[locale] || {}; - const polyglot = new Polyglot({ - locale, - phrases: { ...defaultMessages, ...userMessages }, - }); +const withI18nContext = withContext( + { + translate: PropTypes.func.isRequired, + locale: PropTypes.string.isRequired, + }, + ({ locale, messages = {} }) => { + const userMessages = messages[locale] || {}; + const polyglot = new Polyglot({ + locale, + phrases: { ...defaultMessages, ...userMessages }, + }); - return { - locale, - translate: polyglot.t.bind(polyglot), - }; -}); + return { + locale, + translate: polyglot.t.bind(polyglot), + }; + } +); const TranslationProvider = ({ children }) => Children.only(children); @@ -32,4 +35,6 @@ TranslationProvider.propTypes = { const mapStateToProps = state => ({ locale: state.locale }); -export default compose(connect(mapStateToProps), withI18nContext)(TranslationProvider); +export default compose(connect(mapStateToProps), withI18nContext)( + TranslationProvider +); diff --git a/src/i18n/TranslationUtils.js b/src/i18n/TranslationUtils.js index 1339e1da..362d5c79 100644 --- a/src/i18n/TranslationUtils.js +++ b/src/i18n/TranslationUtils.js @@ -4,5 +4,6 @@ export const resolveBrowserLocale = (defaultLocale = DEFAULT_LOCALE) => { // from http://blog.ksol.fr/user-locale-detection-browser-javascript/ // Rely on the window.navigator object to determine user locale const { language, browserLanguage, userLanguage } = window.navigator; - return (language || browserLanguage || userLanguage || defaultLocale).split('-')[0]; + return (language || browserLanguage || userLanguage || defaultLocale) + .split('-')[0]; }; diff --git a/src/i18n/TranslationUtils.spec.js b/src/i18n/TranslationUtils.spec.js index eb5acba5..d4ff0c06 100644 --- a/src/i18n/TranslationUtils.spec.js +++ b/src/i18n/TranslationUtils.spec.js @@ -8,7 +8,7 @@ describe('TranslationUtils', () => { global.window = {}; }); - it('should return default locale if there\'s no available locale in browser', () => { + it("should return default locale if there's no available locale in browser", () => { window.navigator = {}; assert(resolveBrowserLocale(), DEFAULT_LOCALE); }); diff --git a/src/i18n/messages.js b/src/i18n/messages.js index ebc4023a..44d23ef2 100644 --- a/src/i18n/messages.js +++ b/src/i18n/messages.js @@ -26,12 +26,15 @@ export default { }, input: { file: { - upload_several: 'Drop some files to upload, or click to select one.', + upload_several: + 'Drop some files to upload, or click to select one.', upload_single: 'Drop a file to upload, or click to select it.', }, image: { - upload_several: 'Drop some pictures to upload, or click to select one.', - upload_single: 'Drop a picture to upload, or click to select it.', + upload_several: + 'Drop some pictures to upload, or click to select one.', + upload_single: + 'Drop a picture to upload, or click to select it.', }, }, message: { diff --git a/src/i18n/translate.js b/src/i18n/translate.js index 06974d63..d64b3987 100644 --- a/src/i18n/translate.js +++ b/src/i18n/translate.js @@ -1,7 +1,7 @@ import PropTypes from 'prop-types'; import { getContext } from 'recompose'; -const translate = (BaseComponent) => { +const translate = BaseComponent => { const TranslatedComponent = getContext({ translate: PropTypes.func.isRequired, locale: PropTypes.string.isRequired, diff --git a/src/mui/auth/Login.js b/src/mui/auth/Login.js index 774b96f8..b9b20fc7 100644 --- a/src/mui/auth/Login.js +++ b/src/mui/auth/Login.js @@ -44,27 +44,32 @@ const styles = { function getColorsFromTheme(theme) { if (!theme) return { primary1Color: cyan500, accent1Color: pinkA200 }; - const { - palette: { - primary1Color, - accent1Color, - }, - } = theme; + const { palette: { primary1Color, accent1Color } } = theme; return { primary1Color, accent1Color }; } // see http://redux-form.com/6.4.3/examples/material-ui/ -const renderInput = ({ meta: { touched, error } = {}, input: { ...inputProps }, ...props }) => +const renderInput = ({ + meta: { touched, error } = {}, + input: { ...inputProps }, + ...props +}) => ( ; + /> +); class Login extends Component { - - login = (auth) => this.props.userLogin(auth, this.props.location.state ? this.props.location.state.nextPathname : '/'); + login = auth => + this.props.userLogin( + auth, + this.props.location.state + ? this.props.location.state.nextPathname + : '/' + ); render() { const { handleSubmit, isLoading, theme, translate } = this.props; @@ -75,23 +80,31 @@ class Login extends Component {
- } size={60} /> + } + size={60} + />
-
+
@@ -101,8 +114,15 @@ class Login extends Component { } + disabled={submitting} + icon={ + submitting && ( + + ) + } label={translate('aor.auth.sign_in')} fullWidth /> @@ -129,7 +149,7 @@ Login.defaultProps = { theme: defaultTheme, }; -const mapStateToProps = state => ({ isLoading: state.admin.loading > 0 }) +const mapStateToProps = state => ({ isLoading: state.admin.loading > 0 }); const enhance = compose( translate, @@ -138,12 +158,14 @@ const enhance = compose( validate: (values, props) => { const errors = {}; const { translate } = props; - if (!values.username) errors.username = translate('aor.validation.required'); - if (!values.password) errors.password = translate('aor.validation.required'); + if (!values.username) + errors.username = translate('aor.validation.required'); + if (!values.password) + errors.password = translate('aor.validation.required'); return errors; }, }), - connect(mapStateToProps, { userLogin: userLoginAction }), + connect(mapStateToProps, { userLogin: userLoginAction }) ); export default enhance(Login); diff --git a/src/mui/auth/Logout.js b/src/mui/auth/Logout.js index 3a7f69b9..889d3883 100644 --- a/src/mui/auth/Logout.js +++ b/src/mui/auth/Logout.js @@ -9,14 +9,13 @@ import ExitIcon from 'material-ui/svg-icons/action/power-settings-new'; import translate from '../../i18n/translate'; import { userLogout as userLogoutAction } from '../../actions/authActions'; -const Logout = ({ translate, userLogout }) => ( +const Logout = ({ translate, userLogout }) => } primaryText={translate('aor.auth.logout')} onClick={userLogout} - /> -); + />; Logout.propTypes = { translate: PropTypes.func, @@ -29,7 +28,7 @@ const mapStateToProps = state => ({ const enhance = compose( translate, - connect(mapStateToProps, { userLogout: userLogoutAction }), + connect(mapStateToProps, { userLogout: userLogoutAction }) ); export default enhance(Logout); diff --git a/src/mui/button/CreateButton.js b/src/mui/button/CreateButton.js index 04c4e0eb..d731c13e 100644 --- a/src/mui/button/CreateButton.js +++ b/src/mui/button/CreateButton.js @@ -23,23 +23,26 @@ const styles = { }, }; -const CreateButton = ({ basePath = '', translate, label = 'aor.action.create', width }) => +const CreateButton = ({ + basePath = '', + translate, + label = 'aor.action.create', + width, +}) => width === 1 - ? - } - > - - - : - } - containerElement={} - style={styles.flat} - />; + ? } + > + + + : } + containerElement={} + style={styles.flat} + />; CreateButton.propTypes = { basePath: PropTypes.string, @@ -51,7 +54,7 @@ CreateButton.propTypes = { const enhance = compose( onlyUpdateForKeys(['basePath, label']), withWidth(), - translate, + translate ); export default enhance(CreateButton); diff --git a/src/mui/button/DeleteButton.js b/src/mui/button/DeleteButton.js index b2654f44..94adb2ae 100644 --- a/src/mui/button/DeleteButton.js +++ b/src/mui/button/DeleteButton.js @@ -6,13 +6,21 @@ import ActionDelete from 'material-ui/svg-icons/action/delete'; import linkToRecord from '../../util/linkToRecord'; import translate from '../../i18n/translate'; -const DeleteButton = ({ basePath = '', label = 'aor.action.delete', record = {}, translate }) => } - containerElement={} - style={{ overflow: 'inherit' }} -/>; +const DeleteButton = ({ + basePath = '', + label = 'aor.action.delete', + record = {}, + translate, +}) => + } + containerElement={ + + } + style={{ overflow: 'inherit' }} + />; DeleteButton.propTypes = { basePath: PropTypes.string, diff --git a/src/mui/button/EditButton.js b/src/mui/button/EditButton.js index e226f930..c156efc7 100644 --- a/src/mui/button/EditButton.js +++ b/src/mui/button/EditButton.js @@ -8,13 +8,19 @@ import ContentCreate from 'material-ui/svg-icons/content/create'; import linkToRecord from '../../util/linkToRecord'; import translate from '../../i18n/translate'; -const EditButton = ({ basePath = '', label = 'aor.action.edit', record = {}, translate }) => } - containerElement={} - style={{ overflow: 'inherit' }} -/>; +const EditButton = ({ + basePath = '', + label = 'aor.action.edit', + record = {}, + translate, +}) => + } + containerElement={} + style={{ overflow: 'inherit' }} + />; EditButton.propTypes = { basePath: PropTypes.string, @@ -24,13 +30,13 @@ EditButton.propTypes = { }; const enhance = compose( - shouldUpdate((props, nextProps) => - (props.record - && props.record.id !== nextProps.record.id - || props.basePath !== nextProps.basePath) - || (props.record == null && nextProps.record != null) + shouldUpdate( + (props, nextProps) => + (props.record && props.record.id !== nextProps.record.id) || + props.basePath !== nextProps.basePath || + (props.record == null && nextProps.record != null) ), - translate, + translate ); export default enhance(EditButton); diff --git a/src/mui/button/ListButton.js b/src/mui/button/ListButton.js index b257f886..b6c688a5 100644 --- a/src/mui/button/ListButton.js +++ b/src/mui/button/ListButton.js @@ -5,13 +5,14 @@ import FlatButton from 'material-ui/FlatButton'; import ActionList from 'material-ui/svg-icons/action/list'; import translate from '../../i18n/translate'; -const ListButton = ({ basePath = '', label = 'aor.action.list', translate }) => } - containerElement={} - style={{ overflow: 'inherit' }} -/>; +const ListButton = ({ basePath = '', label = 'aor.action.list', translate }) => + } + containerElement={} + style={{ overflow: 'inherit' }} + />; ListButton.propTypes = { basePath: PropTypes.string, diff --git a/src/mui/button/RefreshButton.js b/src/mui/button/RefreshButton.js index 09909c78..659fef65 100644 --- a/src/mui/button/RefreshButton.js +++ b/src/mui/button/RefreshButton.js @@ -4,12 +4,13 @@ import FlatButton from 'material-ui/FlatButton'; import NavigationRefresh from 'material-ui/svg-icons/navigation/refresh'; import translate from '../../i18n/translate'; -const RefreshButton = ({ label = 'aor.action.refresh', translate, refresh }) => } -/>; +const RefreshButton = ({ label = 'aor.action.refresh', translate, refresh }) => + } + />; RefreshButton.propTypes = { label: PropTypes.string, diff --git a/src/mui/button/SaveButton.js b/src/mui/button/SaveButton.js index cacc535e..2e724240 100644 --- a/src/mui/button/SaveButton.js +++ b/src/mui/button/SaveButton.js @@ -8,8 +8,7 @@ import CircularProgress from 'material-ui/CircularProgress'; import translate from '../../i18n/translate'; export class SaveButton extends Component { - - handleClick = (e) => { + handleClick = e => { if (this.props.saving) { // prevent double submission e.preventDefault(); @@ -21,17 +20,28 @@ export class SaveButton extends Component { } handleSubmitWithRedirect(redirect)(); } - } + }; render() { - const { saving, label = 'aor.action.save', raised = true, translate, submitOnEnter, redirect } = this.props; + const { + saving, + label = 'aor.action.save', + raised = true, + translate, + submitOnEnter, + redirect, + } = this.props; const type = submitOnEnter ? 'submit' : 'button'; const ButtonComponent = raised ? RaisedButton : FlatButton; return ( : } + icon={ + saving && saving.redirect === redirect + ? + : + } onClick={this.handleClick} primary={!saving} style={{ @@ -46,17 +56,11 @@ export class SaveButton extends Component { SaveButton.propTypes = { label: PropTypes.string, raised: PropTypes.bool, - saving: PropTypes.oneOfType([ - PropTypes.object, - PropTypes.bool, - ]), + saving: PropTypes.oneOfType([PropTypes.object, PropTypes.bool]), translate: PropTypes.func.isRequired, submitOnEnter: PropTypes.bool, handleSubmitWithRedirect: PropTypes.func, - redirect: PropTypes.oneOfType([ - PropTypes.string, - PropTypes.bool, - ]), + redirect: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]), }; SaveButton.defaultProps = { diff --git a/src/mui/button/SaveButton.spec.js b/src/mui/button/SaveButton.spec.js index 01c67782..20beee21 100644 --- a/src/mui/button/SaveButton.spec.js +++ b/src/mui/button/SaveButton.spec.js @@ -5,7 +5,7 @@ import sinon from 'sinon'; import { SaveButton } from './SaveButton'; -const translate = (label) => label; +const translate = label => label; describe('', () => { it('should render when raised is true', () => { @@ -26,10 +26,18 @@ describe('', () => { it('should render as submit type when submitOnEnter is true', () => { const raisedButtonWrapper = shallow( - + ); const flatButtonWrapper = shallow( - + ); assert.equal(raisedButtonWrapper.prop('type'), 'submit'); @@ -38,10 +46,18 @@ describe('', () => { it('should render as button type when submitOnEnter is false', () => { const raisedButtonWrapper = shallow( - + ); const flatButtonWrapper = shallow( - + ); assert.equal(raisedButtonWrapper.prop('type'), 'button'); @@ -51,10 +67,20 @@ describe('', () => { it('should trigger submit action when clicked if no saving is in progress', () => { const onSubmit = sinon.spy(); const raisedButtonWrapper = shallow( - onSubmit} saving={false} /> + onSubmit} + saving={false} + /> ); const flatButtonWrapper = shallow( - onSubmit} saving={false} /> + onSubmit} + saving={false} + /> ); raisedButtonWrapper.simulate('click'); @@ -68,10 +94,20 @@ describe('', () => { const event = { preventDefault: sinon.spy() }; const raisedButtonWrapper = shallow( - onSubmit} saving={true} /> + onSubmit} + saving={true} + /> ); const flatButtonWrapper = shallow( - onSubmit} saving={true} /> + onSubmit} + saving={true} + /> ); raisedButtonWrapper.simulate('click', event); diff --git a/src/mui/button/ShowButton.js b/src/mui/button/ShowButton.js index 7354ce01..7cecd954 100644 --- a/src/mui/button/ShowButton.js +++ b/src/mui/button/ShowButton.js @@ -8,13 +8,21 @@ import ImageEye from 'material-ui/svg-icons/image/remove-red-eye'; import linkToRecord from '../../util/linkToRecord'; import translate from '../../i18n/translate'; -const ShowButton = ({ basePath = '', label = 'aor.action.show', record = {}, translate }) => } - containerElement={} - style={{ overflow: 'inherit' }} -/>; +const ShowButton = ({ + basePath = '', + label = 'aor.action.show', + record = {}, + translate, +}) => + } + containerElement={ + + } + style={{ overflow: 'inherit' }} + />; ShowButton.propTypes = { basePath: PropTypes.string, @@ -24,13 +32,13 @@ ShowButton.propTypes = { }; const enhance = compose( - shouldUpdate((props, nextProps) => - (props.record - && props.record.id !== nextProps.record.id - || props.basePath !== nextProps.basePath) - || (props.record == null && nextProps.record != null) + shouldUpdate( + (props, nextProps) => + (props.record && props.record.id !== nextProps.record.id) || + props.basePath !== nextProps.basePath || + (props.record == null && nextProps.record != null) ), - translate, + translate ); export default enhance(ShowButton); diff --git a/src/mui/delete/Delete.js b/src/mui/delete/Delete.js index 95f884cd..f780a1d1 100644 --- a/src/mui/delete/Delete.js +++ b/src/mui/delete/Delete.js @@ -11,7 +11,10 @@ import inflection from 'inflection'; import ViewTitle from '../layout/ViewTitle'; import Title from '../layout/Title'; import { ListButton } from '../button'; -import { crudGetOne as crudGetOneAction, crudDelete as crudDeleteAction } from '../../actions/dataActions'; +import { + crudGetOne as crudGetOneAction, + crudDelete as crudDeleteAction, +} from '../../actions/dataActions'; import translate from '../../i18n/translate'; const styles = { @@ -28,12 +31,20 @@ class Delete extends Component { } componentDidMount() { - this.props.crudGetOne(this.props.resource, this.props.id, this.getBasePath()); + this.props.crudGetOne( + this.props.resource, + this.props.id, + this.getBasePath() + ); } componentWillReceiveProps(nextProps) { if (this.props.id !== nextProps.id) { - this.props.crudGetOne(nextProps.resource, nextProps.id, this.getBasePath()); + this.props.crudGetOne( + nextProps.resource, + nextProps.id, + this.getBasePath() + ); } } @@ -44,7 +55,11 @@ class Delete extends Component { handleSubmit(event) { event.preventDefault(); - this.props.crudDelete(this.props.resource, this.props.id, this.getBasePath()); + this.props.crudDelete( + this.props.resource, + this.props.id, + this.getBasePath() + ); } goBack() { @@ -64,17 +79,21 @@ class Delete extends Component { id, data, }); - const titleElement = data ? : ''; + const titleElement = data + ? <Title title={title} record={data} defaultTitle={defaultTitle} /> + : ''; return ( <div> - <Card style={{ opacity: isLoading ? .8 : 1 }}> + <Card style={{ opacity: isLoading ? 0.8 : 1 }}> <CardActions style={styles.actions}> <ListButton basePath={basePath} /> </CardActions> <ViewTitle title={titleElement} /> <form onSubmit={this.handleSubmit}> - <CardText>{translate('aor.message.are_you_sure')}</CardText> + <CardText> + {translate('aor.message.are_you_sure')} + </CardText> <Toolbar style={styles.toolbar}> <ToolbarGroup> <RaisedButton @@ -116,17 +135,20 @@ Delete.propTypes = { function mapStateToProps(state, props) { return { id: decodeURIComponent(props.match.params.id), - data: state.admin[props.resource].data[decodeURIComponent(props.match.params.id)], + data: + state.admin[props.resource].data[ + decodeURIComponent(props.match.params.id) + ], isLoading: state.admin.loading > 0, }; } const enhance = compose( - connect( - mapStateToProps, - { crudGetOne: crudGetOneAction, crudDelete: crudDeleteAction } - ), - translate, + connect(mapStateToProps, { + crudGetOne: crudGetOneAction, + crudDelete: crudDeleteAction, + }), + translate ); export default enhance(Delete); diff --git a/src/mui/detail/Create.js b/src/mui/detail/Create.js index f3ad4e0c..f99a17cc 100644 --- a/src/mui/detail/Create.js +++ b/src/mui/detail/Create.js @@ -24,11 +24,23 @@ class Create extends Component { } save = (record, redirect) => { - this.props.crudCreate(this.props.resource, record, this.getBasePath(), redirect); - } + this.props.crudCreate( + this.props.resource, + record, + this.getBasePath(), + redirect + ); + }; render() { - const { actions = <DefaultActions />, children, isLoading, resource, title, translate } = this.props; + const { + actions = <DefaultActions />, + children, + isLoading, + resource, + title, + translate, + } = this.props; const basePath = this.getBasePath(); const resourceName = translate(`resources.${resource}.name`, { @@ -38,15 +50,18 @@ class Create extends Component { const defaultTitle = translate('aor.page.create', { name: `${resourceName}`, }); - const titleElement = <Title title={title} defaultTitle={defaultTitle} />; + const titleElement = ( + <Title title={title} defaultTitle={defaultTitle} /> + ); return ( <div className="create-page"> <Card style={{ opacity: isLoading ? 0.8 : 1 }}> - {actions && React.cloneElement(actions, { - basePath, - resource, - })} + {actions && + React.cloneElement(actions, { + basePath, + resource, + })} <ViewTitle title={titleElement} /> {React.cloneElement(children, { save: this.save, @@ -54,7 +69,10 @@ class Create extends Component { basePath, record: {}, translate, - redirect: typeof children.props.redirect === 'undefined' ? this.defaultRedirectRoute() : children.props.redirect, + redirect: + typeof children.props.redirect === 'undefined' + ? this.defaultRedirectRoute() + : children.props.redirect, })} </Card> </div> @@ -84,11 +102,8 @@ function mapStateToProps(state) { } const enhance = compose( - connect( - mapStateToProps, - { crudCreate: crudCreateAction }, - ), - translate, + connect(mapStateToProps, { crudCreate: crudCreateAction }), + translate ); export default enhance(Create); diff --git a/src/mui/detail/CreateActions.js b/src/mui/detail/CreateActions.js index 8c1d31c6..92bcb1df 100644 --- a/src/mui/detail/CreateActions.js +++ b/src/mui/detail/CreateActions.js @@ -8,10 +8,9 @@ const cardActionStyle = { float: 'right', }; -const CreateActions = ({ basePath }) => ( +const CreateActions = ({ basePath }) => <CardActions style={cardActionStyle}> <ListButton basePath={basePath} /> - </CardActions> -); + </CardActions>; export default CreateActions; diff --git a/src/mui/detail/Edit.js b/src/mui/detail/Edit.js index 1144126f..4045bb74 100644 --- a/src/mui/detail/Edit.js +++ b/src/mui/detail/Edit.js @@ -6,7 +6,10 @@ import compose from 'recompose/compose'; import inflection from 'inflection'; import ViewTitle from '../layout/ViewTitle'; import Title from '../layout/Title'; -import { crudGetOne as crudGetOneAction, crudUpdate as crudUpdateAction } from '../../actions/dataActions'; +import { + crudGetOne as crudGetOneAction, + crudUpdate as crudUpdateAction, +} from '../../actions/dataActions'; import DefaultActions from './EditActions'; import translate from '../../i18n/translate'; @@ -50,18 +53,36 @@ export class Edit extends Component { this.props.crudGetOne(resource, id, this.getBasePath()); } - refresh = (event) => { + refresh = event => { event.stopPropagation(); this.fullRefresh = true; this.updateData(); - } + }; save = (record, redirect) => { - this.props.crudUpdate(this.props.resource, this.props.id, record, this.props.data, this.getBasePath(), redirect); - } + this.props.crudUpdate( + this.props.resource, + this.props.id, + record, + this.props.data, + this.getBasePath(), + redirect + ); + }; render() { - const { actions = <DefaultActions />, children, data, hasDelete, hasShow, id, isLoading, resource, title, translate } = this.props; + const { + actions = <DefaultActions />, + children, + data, + hasDelete, + hasShow, + id, + isLoading, + resource, + title, + translate, + } = this.props; const { key } = this.state; const basePath = this.getBasePath(); @@ -74,7 +95,9 @@ export class Edit extends Component { id, data, }); - const titleElement = data ? <Title title={title} record={data} defaultTitle={defaultTitle} /> : ''; + const titleElement = data + ? <Title title={title} record={data} defaultTitle={defaultTitle} /> + : ''; // using this.previousKey instead of this.fullRefresh makes // the new form mount, the old form unmount, and the new form update appear in the same frame // so the form doesn't disappear while refreshing @@ -84,23 +107,29 @@ export class Edit extends Component { return ( <div className="edit-page"> <Card style={{ opacity: isLoading ? 0.8 : 1 }} key={key}> - {actions && React.cloneElement(actions, { - basePath, - data, - hasDelete, - hasShow, - refresh: this.refresh, - resource, - })} + {actions && + React.cloneElement(actions, { + basePath, + data, + hasDelete, + hasShow, + refresh: this.refresh, + resource, + })} <ViewTitle title={titleElement} /> - {data && !isRefreshing && React.cloneElement(children, { - save: this.save, - resource, - basePath, - record: data, - translate, - redirect: typeof children.props.redirect === 'undefined' ? this.defaultRedirectRoute() : children.props.redirect, - })} + {data && + !isRefreshing && + React.cloneElement(children, { + save: this.save, + resource, + basePath, + record: data, + translate, + redirect: + typeof children.props.redirect === 'undefined' + ? this.defaultRedirectRoute() + : children.props.redirect, + })} {!data && <CardText> </CardText>} </Card> </div> @@ -128,17 +157,20 @@ Edit.propTypes = { function mapStateToProps(state, props) { return { id: decodeURIComponent(props.match.params.id), - data: state.admin[props.resource].data[decodeURIComponent(props.match.params.id)], + data: + state.admin[props.resource].data[ + decodeURIComponent(props.match.params.id) + ], isLoading: state.admin.loading > 0, }; } const enhance = compose( - connect( - mapStateToProps, - { crudGetOne: crudGetOneAction, crudUpdate: crudUpdateAction }, - ), - translate, + connect(mapStateToProps, { + crudGetOne: crudGetOneAction, + crudUpdate: crudUpdateAction, + }), + translate ); export default enhance(Edit); diff --git a/src/mui/detail/Edit.spec.js b/src/mui/detail/Edit.spec.js index 0145c9ef..c2c7b87b 100644 --- a/src/mui/detail/Edit.spec.js +++ b/src/mui/detail/Edit.spec.js @@ -21,20 +21,26 @@ describe('<Edit />', () => { }; it('should display correctly when called with a child', () => { - const Foo = () => <div/>; - const wrapper = shallow(<Edit {...defaultProps}><Foo/></Edit>); + const Foo = () => <div />; + const wrapper = shallow( + <Edit {...defaultProps}> + <Foo /> + </Edit> + ); const inner = wrapper.find('Foo'); assert.equal(inner.length, 1); }); it('should display children inputs of SimpleForm', () => { - const wrapper = shallow(<Edit {...defaultProps}> - <SimpleForm> - <TextInput source="foo" /> - <TextInput source="bar" /> - </SimpleForm> - </Edit>); + const wrapper = shallow( + <Edit {...defaultProps}> + <SimpleForm> + <TextInput source="foo" /> + <TextInput source="bar" /> + </SimpleForm> + </Edit> + ); const inputs = wrapper.find('TextInput'); assert.deepEqual(inputs.map(i => i.prop('source')), ['foo', 'bar']); }); diff --git a/src/mui/detail/EditActions.js b/src/mui/detail/EditActions.js index d0d36483..9ace2067 100644 --- a/src/mui/detail/EditActions.js +++ b/src/mui/detail/EditActions.js @@ -8,13 +8,12 @@ const cardActionStyle = { float: 'right', }; -const EditActions = ({ basePath, data, hasDelete, hasShow, refresh }) => ( +const EditActions = ({ basePath, data, hasDelete, hasShow, refresh }) => <CardActions style={cardActionStyle}> {hasShow && <ShowButton basePath={basePath} record={data} />} <ListButton basePath={basePath} /> {hasDelete && <DeleteButton basePath={basePath} record={data} />} <RefreshButton refresh={refresh} /> - </CardActions> -); + </CardActions>; export default EditActions; diff --git a/src/mui/detail/Show.js b/src/mui/detail/Show.js index 4582e071..a57566f9 100644 --- a/src/mui/detail/Show.js +++ b/src/mui/detail/Show.js @@ -30,14 +30,25 @@ export class Show extends Component { this.props.crudGetOne(resource, id, this.getBasePath()); } - refresh = (event) => { + refresh = event => { event.stopPropagation(); this.fullRefresh = true; this.updateData(); - } + }; render() { - const { actions = <DefaultActions />, title, children, id, data, isLoading, resource, hasDelete, hasEdit, translate } = this.props; + const { + actions = <DefaultActions />, + title, + children, + id, + data, + isLoading, + resource, + hasDelete, + hasEdit, + translate, + } = this.props; const basePath = this.getBasePath(); const resourceName = translate(`resources.${resource}.name`, { @@ -49,26 +60,30 @@ export class Show extends Component { id, data, }); - const titleElement = data ? <Title title={title} record={data} defaultTitle={defaultTitle} /> : ''; + const titleElement = data + ? <Title title={title} record={data} defaultTitle={defaultTitle} /> + : ''; return ( <div> <Card style={{ opacity: isLoading ? 0.8 : 1 }}> - {actions && React.cloneElement(actions, { - basePath, - data, - hasDelete, - hasEdit, - refresh: this.refresh, - resource, - })} + {actions && + React.cloneElement(actions, { + basePath, + data, + hasDelete, + hasEdit, + refresh: this.refresh, + resource, + })} <ViewTitle title={titleElement} /> - {data && React.cloneElement(children, { - resource, - basePath, - record: data, - translate, - })} + {data && + React.cloneElement(children, { + resource, + basePath, + record: data, + translate, + })} </Card> </div> ); @@ -94,17 +109,17 @@ Show.propTypes = { function mapStateToProps(state, props) { return { id: decodeURIComponent(props.match.params.id), - data: state.admin[props.resource].data[decodeURIComponent(props.match.params.id)], + data: + state.admin[props.resource].data[ + decodeURIComponent(props.match.params.id) + ], isLoading: state.admin.loading > 0, }; } const enhance = compose( - connect( - mapStateToProps, - { crudGetOne: crudGetOneAction }, - ), - translate, + connect(mapStateToProps, { crudGetOne: crudGetOneAction }), + translate ); export default enhance(Show); diff --git a/src/mui/detail/Show.spec.js b/src/mui/detail/Show.spec.js index 2dfd4967..356b7c8f 100644 --- a/src/mui/detail/Show.spec.js +++ b/src/mui/detail/Show.spec.js @@ -20,20 +20,26 @@ describe('<Show />', () => { }; it('should display correctly when called with a child', () => { - const Foo = () => <div/>; - const wrapper = shallow(<Show {...defaultProps}><Foo/></Show>); + const Foo = () => <div />; + const wrapper = shallow( + <Show {...defaultProps}> + <Foo /> + </Show> + ); const inner = wrapper.find('Foo'); assert.equal(inner.length, 1); }); it('should display children inputs of SimpleShowLayout', () => { - const wrapper = shallow(<Show {...defaultProps}> - <SimpleShowLayout> - <TextField source="foo" /> - <TextField source="bar" /> - </SimpleShowLayout> - </Show>); + const wrapper = shallow( + <Show {...defaultProps}> + <SimpleShowLayout> + <TextField source="foo" /> + <TextField source="bar" /> + </SimpleShowLayout> + </Show> + ); const inputs = wrapper.find('pure(TextField)'); assert.deepEqual(inputs.map(i => i.prop('source')), ['foo', 'bar']); }); diff --git a/src/mui/detail/ShowActions.js b/src/mui/detail/ShowActions.js index 4f0b00f1..bfa5a4ff 100644 --- a/src/mui/detail/ShowActions.js +++ b/src/mui/detail/ShowActions.js @@ -8,13 +8,12 @@ const cardActionStyle = { float: 'right', }; -const ShowActions = ({ basePath, data, hasDelete, hasEdit, refresh }) => ( +const ShowActions = ({ basePath, data, hasDelete, hasEdit, refresh }) => <CardActions style={cardActionStyle}> {hasEdit && <EditButton basePath={basePath} record={data} />} <ListButton basePath={basePath} /> {hasDelete && <DeleteButton basePath={basePath} record={data} />} <RefreshButton refresh={refresh} /> - </CardActions> -); + </CardActions>; export default ShowActions; diff --git a/src/mui/detail/SimpleShowLayout.js b/src/mui/detail/SimpleShowLayout.js index 91e9127d..03bf9be7 100644 --- a/src/mui/detail/SimpleShowLayout.js +++ b/src/mui/detail/SimpleShowLayout.js @@ -3,21 +3,41 @@ import PropTypes from 'prop-types'; import Labeled from '../input/Labeled'; const defaultStyle = { padding: '0 1em 1em 1em' }; -export const SimpleShowLayout = ({ basePath, children, record, resource, style = defaultStyle }) => ( +export const SimpleShowLayout = ({ + basePath, + children, + record, + resource, + style = defaultStyle, +}) => <div style={style}> - {Children.map(children, field => ( - <div key={field.props.source} style={field.props.style} className={`aor-field-${field.props.source}`}> - {field.props.addLabel ? - <Labeled record={record} resource={resource} basePath={basePath} label={field.props.label} source={field.props.source} disabled={false}>{field}</Labeled> : - (typeof field.type === 'string' ? - field : - React.cloneElement(field, { record, resource, basePath }) - ) - } + {Children.map(children, field => + <div + key={field.props.source} + style={field.props.style} + className={`aor-field-${field.props.source}`} + > + {field.props.addLabel + ? <Labeled + record={record} + resource={resource} + basePath={basePath} + label={field.props.label} + source={field.props.source} + disabled={false} + > + {field} + </Labeled> + : typeof field.type === 'string' + ? field + : React.cloneElement(field, { + record, + resource, + basePath, + })} </div> - ))} - </div> -); + )} + </div>; SimpleShowLayout.propTypes = { basePath: PropTypes.string, diff --git a/src/mui/detail/Tab.js b/src/mui/detail/Tab.js index 958e994c..3572a2f0 100644 --- a/src/mui/detail/Tab.js +++ b/src/mui/detail/Tab.js @@ -1,20 +1,30 @@ import React from 'react'; import Labeled from '../input/Labeled'; -const Tab = ({ label, icon, children, ...rest }) => <span> - {React.Children.map(children, field => field && ( - <div key={field.props.source} style={field.props.style} className={`aor-field-${field.props.source}`}> - {field.props.addLabel ? - <Labeled - {...rest} - label={field.props.label} - source={field.props.source} - > - {field} - </Labeled> : - ((typeof field.type === 'string') ? field : React.cloneElement(field, rest))} - </div> - ))} -</span>; +const Tab = ({ label, icon, children, ...rest }) => + <span> + {React.Children.map( + children, + field => + field && + <div + key={field.props.source} + style={field.props.style} + className={`aor-field-${field.props.source}`} + > + {field.props.addLabel + ? <Labeled + {...rest} + label={field.props.label} + source={field.props.source} + > + {field} + </Labeled> + : typeof field.type === 'string' + ? field + : React.cloneElement(field, rest)} + </div> + )} + </span>; export default Tab; diff --git a/src/mui/detail/TabbedShowLayout.js b/src/mui/detail/TabbedShowLayout.js index 17af8253..26daa83b 100644 --- a/src/mui/detail/TabbedShowLayout.js +++ b/src/mui/detail/TabbedShowLayout.js @@ -14,20 +14,42 @@ export class TabbedShowLayout extends Component { }; } - handleChange = (value) => { + handleChange = value => { this.setState({ value }); }; render() { - const { children, contentContainerStyle, record, resource, basePath, translate } = this.props; + const { + children, + contentContainerStyle, + record, + resource, + basePath, + translate, + } = this.props; return ( <div style={divStyle}> - <Tabs value={this.state.value} onChange={this.handleChange} contentContainerStyle={contentContainerStyle}> - {React.Children.map(children, (tab, index) => - <Tab key={tab.props.value} label={translate(tab.props.label, { _: tab.props.label })} value={index} icon={tab.props.icon}> - {React.cloneElement(tab, { resource, record, basePath })} - </Tab>, - )} + <Tabs + value={this.state.value} + onChange={this.handleChange} + contentContainerStyle={contentContainerStyle} + > + {React.Children.map(children, (tab, index) => ( + <Tab + key={tab.props.value} + label={translate(tab.props.label, { + _: tab.props.label, + })} + value={index} + icon={tab.props.icon} + > + {React.cloneElement(tab, { + resource, + record, + basePath, + })} + </Tab> + ))} </Tabs> </div> ); @@ -50,7 +72,7 @@ TabbedShowLayout.defaultProps = { const enhance = compose( connect((state, props) => ({ initialValues: getDefaultValues(state, props), - })), + })) ); export default enhance(TabbedShowLayout); diff --git a/src/mui/field/BooleanField.spec.js b/src/mui/field/BooleanField.spec.js index 743fca8c..7100af7c 100644 --- a/src/mui/field/BooleanField.spec.js +++ b/src/mui/field/BooleanField.spec.js @@ -4,28 +4,49 @@ import { shallow } from 'enzyme'; import { BooleanField } from './BooleanField'; describe('<BooleanField />', () => { - it('should display tick if value is true', () => assert.ok( - shallow(<BooleanField record={{ published: true }} source="published" />) - .is('ActionDone') - )); + it('should display tick if value is true', () => + assert.ok( + shallow( + <BooleanField record={{ published: true }} source="published" /> + ).is('ActionDone') + )); - it('should display cross if value is false', () => assert.ok( - shallow(<BooleanField record={{ published: false }} source="published" />) - .is('ContentClear') - )); + it('should display cross if value is false', () => + assert.ok( + shallow( + <BooleanField + record={{ published: false }} + source="published" + /> + ).is('ContentClear') + )); - it('should not display anything if value is null', () => assert.ok( - shallow(<BooleanField record={{ published: null }} source="published" />) - .is('span') - )); + it('should not display anything if value is null', () => + assert.ok( + shallow( + <BooleanField record={{ published: null }} source="published" /> + ).is('span') + )); - it('should use custom styles passed as an elStyle prop', () => assert.deepEqual( - shallow(<BooleanField record={{ foo: true }} source="foo" elStyle={{ margin: 1 }} />).prop('style'), - { margin: 1 }, - )); + it('should use custom styles passed as an elStyle prop', () => + assert.deepEqual( + shallow( + <BooleanField + record={{ foo: true }} + source="foo" + elStyle={{ margin: 1 }} + /> + ).prop('style'), + { margin: 1 } + )); - it('should handle deep fields', () => assert.ok( - shallow(<BooleanField record={{ foo: { bar: true } }} source="foo.bar" />) - .is('ActionDone') - )); + it('should handle deep fields', () => + assert.ok( + shallow( + <BooleanField + record={{ foo: { bar: true } }} + source="foo.bar" + /> + ).is('ActionDone') + )); }); diff --git a/src/mui/field/ChipField.js b/src/mui/field/ChipField.js index f9cbfcab..29a8c205 100644 --- a/src/mui/field/ChipField.js +++ b/src/mui/field/ChipField.js @@ -5,7 +5,9 @@ import pure from 'recompose/pure'; import Chip from 'material-ui/Chip'; const ChipField = ({ source, record = {}, elStyle = { margin: 4 } }) => - <Chip style={elStyle}>{get(record, source)}</Chip>; + <Chip style={elStyle}> + {get(record, source)} + </Chip>; ChipField.propTypes = { addLabel: PropTypes.bool, diff --git a/src/mui/field/DateField.js b/src/mui/field/DateField.js index 955f336a..9c638ff4 100644 --- a/src/mui/field/DateField.js +++ b/src/mui/field/DateField.js @@ -6,9 +6,9 @@ import pure from 'recompose/pure'; const toLocaleStringSupportsLocales = (() => { // from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toLocaleString try { - new Date().toLocaleString("i"); + new Date().toLocaleString('i'); } catch (error) { - return (error instanceof RangeError); + return error instanceof RangeError; } return false; })(); @@ -38,16 +38,31 @@ const toLocaleStringSupportsLocales = (() => { * <span>mercredi 7 novembre 2012</span> */ -export const DateField = ({ elStyle, locales, options, record, showTime = false, source }) => { +export const DateField = ({ + elStyle, + locales, + options, + record, + showTime = false, + source, +}) => { if (!record) return null; const value = get(record, source); if (value == null) return null; const date = value instanceof Date ? value : new Date(value); - const dateString = showTime ? - (toLocaleStringSupportsLocales ? date.toLocaleString(locales, options) : date.toLocaleString()) : - (toLocaleStringSupportsLocales ? date.toLocaleDateString(locales, options) : date.toLocaleDateString()); + const dateString = showTime + ? toLocaleStringSupportsLocales + ? date.toLocaleString(locales, options) + : date.toLocaleString() + : toLocaleStringSupportsLocales + ? date.toLocaleDateString(locales, options) + : date.toLocaleDateString(); - return <span style={elStyle}>{dateString}</span>; + return ( + <span style={elStyle}> + {dateString} + </span> + ); }; DateField.propTypes = { diff --git a/src/mui/field/DateField.spec.js b/src/mui/field/DateField.spec.js index 8d2738f2..b0359521 100644 --- a/src/mui/field/DateField.spec.js +++ b/src/mui/field/DateField.spec.js @@ -4,25 +4,41 @@ import { shallow } from 'enzyme'; import { DateField } from './DateField'; describe('<DateField />', () => { - it('should return null when the record is not set', () => assert.equal( - shallow(<DateField source="foo" />).html(), - null, - )); - - it('should return null when the record has no value for the source', () => assert.equal( - shallow(<DateField record={{}} source="foo" />).html(), - null, - )); - - it('should render a date', () => assert.equal( - shallow(<DateField record={{ foo: new Date('2017-04-23') }} source="foo" locales="en-US" />).html(), - `<span>${new Date('2017-04-23').toLocaleDateString('en-US')}</span>`, - )); - - it('should render a date and time when the showtime prop is passed', () => assert.equal( - shallow(<DateField record={{ foo: new Date('2017-04-23 23:05') }} showTime source="foo" locales="en-US" />).html(), - `<span>${new Date('2017-04-23 23:05').toLocaleString('en-US')}</span>`, - )); + it('should return null when the record is not set', () => + assert.equal(shallow(<DateField source="foo" />).html(), null)); + + it('should return null when the record has no value for the source', () => + assert.equal( + shallow(<DateField record={{}} source="foo" />).html(), + null + )); + + it('should render a date', () => + assert.equal( + shallow( + <DateField + record={{ foo: new Date('2017-04-23') }} + source="foo" + locales="en-US" + /> + ).html(), + `<span>${new Date('2017-04-23').toLocaleDateString('en-US')}</span>` + )); + + it('should render a date and time when the showtime prop is passed', () => + assert.equal( + shallow( + <DateField + record={{ foo: new Date('2017-04-23 23:05') }} + showTime + source="foo" + locales="en-US" + /> + ).html(), + `<span>${new Date('2017-04-23 23:05').toLocaleString( + 'en-US' + )}</span>` + )); it('should pass the options prop to toLocaleString', () => { const date = new Date('2017-04-23'); @@ -33,25 +49,54 @@ describe('<DateField />', () => { day: 'numeric', }; return assert.equal( - shallow(<DateField - record={{ foo: date }} source="foo" locales="en-US" options={options} - />).html(), - `<span>${date.toLocaleDateString('en-US', options)}</span>`, + shallow( + <DateField + record={{ foo: date }} + source="foo" + locales="en-US" + options={options} + /> + ).html(), + `<span>${date.toLocaleDateString('en-US', options)}</span>` ); }); - it('should use the locales props as an argument to toLocaleString', () => assert.equal( - shallow(<DateField record={{ foo: new Date('2017-04-23') }} source="foo" locales="fr-FR" />).html(), - `<span>${new Date('2017-04-23').toLocaleDateString('fr-FR')}</span>`, - )); + it('should use the locales props as an argument to toLocaleString', () => + assert.equal( + shallow( + <DateField + record={{ foo: new Date('2017-04-23') }} + source="foo" + locales="fr-FR" + /> + ).html(), + `<span>${new Date('2017-04-23').toLocaleDateString('fr-FR')}</span>` + )); - it('should use custom styles passed as an elStyle prop', () => assert.equal( - shallow(<DateField record={{ foo: new Date('01/01/2016') }} source="foo" locales="en-US" elStyle={{ margin: 1 }} />).html(), - `<span style="margin:1px;">${new Date('1/1/2016').toLocaleDateString('en-US')}</span>`, - )); + it('should use custom styles passed as an elStyle prop', () => + assert.equal( + shallow( + <DateField + record={{ foo: new Date('01/01/2016') }} + source="foo" + locales="en-US" + elStyle={{ margin: 1 }} + /> + ).html(), + `<span style="margin:1px;">${new Date( + '1/1/2016' + ).toLocaleDateString('en-US')}</span>` + )); - it('should handle deep fields', () => assert.equal( - shallow(<DateField record={{ foo: { bar: new Date('01/01/2016') } }} source="foo.bar" locales="en-US" />).html(), - `<span>${new Date('1/1/2016').toLocaleDateString('en-US')}</span>`, - )); + it('should handle deep fields', () => + assert.equal( + shallow( + <DateField + record={{ foo: { bar: new Date('01/01/2016') } }} + source="foo.bar" + locales="en-US" + /> + ).html(), + `<span>${new Date('1/1/2016').toLocaleDateString('en-US')}</span>` + )); }); diff --git a/src/mui/field/EmailField.js b/src/mui/field/EmailField.js index 562bfb65..b638b549 100644 --- a/src/mui/field/EmailField.js +++ b/src/mui/field/EmailField.js @@ -4,7 +4,9 @@ import get from 'lodash.get'; import pure from 'recompose/pure'; const EmailField = ({ source, record = {}, elStyle }) => - <a style={elStyle} href={`mailto:${get(record, source)}`}>{get(record, source)}</a>; + <a style={elStyle} href={`mailto:${get(record, source)}`}> + {get(record, source)} + </a>; EmailField.propTypes = { addLabel: PropTypes.bool, diff --git a/src/mui/field/EmailField.spec.js b/src/mui/field/EmailField.spec.js index f01b1d13..bde03564 100644 --- a/src/mui/field/EmailField.spec.js +++ b/src/mui/field/EmailField.spec.js @@ -7,18 +7,29 @@ describe('<EmailField />', () => { it('should render as an email link', () => { const record = { foo: 'foo@bar.com' }; const wrapper = shallow(<EmailField record={record} source="foo" />); - assert.equal(wrapper.html(), '<a href="mailto:foo@bar.com">foo@bar.com</a>'); + assert.equal( + wrapper.html(), + '<a href="mailto:foo@bar.com">foo@bar.com</a>' + ); }); it('should handle deep fields', () => { const record = { foo: { bar: 'foo@bar.com' } }; - const wrapper = shallow(<EmailField record={record} source="foo.bar" />); - assert.equal(wrapper.html(), '<a href="mailto:foo@bar.com">foo@bar.com</a>'); + const wrapper = shallow( + <EmailField record={record} source="foo.bar" /> + ); + assert.equal( + wrapper.html(), + '<a href="mailto:foo@bar.com">foo@bar.com</a>' + ); }); it('should display an email (mailto) link', () => { const record = { email: 'hal@kubrickcorp.com' }; const wrapper = shallow(<EmailField record={record} source="email" />); - assert.equal(wrapper.html(), '<a href="mailto:hal@kubrickcorp.com">hal@kubrickcorp.com</a>'); + assert.equal( + wrapper.html(), + '<a href="mailto:hal@kubrickcorp.com">hal@kubrickcorp.com</a>' + ); }); }); diff --git a/src/mui/field/FileField.js b/src/mui/field/FileField.js index ec4c6295..d9a3931b 100644 --- a/src/mui/field/FileField.js +++ b/src/mui/field/FileField.js @@ -18,10 +18,7 @@ export const FileField = ({ elStyle, record, source, title, src }) => { return ( <li key={index}> - <a - href={srcValue} - title={titleValue} - > + <a href={srcValue} title={titleValue}> {titleValue} </a> </li> @@ -33,13 +30,9 @@ export const FileField = ({ elStyle, record, source, title, src }) => { const titleValue = get(record, title) || title; - return ( <div style={elStyle}> - <a - href={sourceValue} - title={titleValue} - > + <a href={sourceValue} title={titleValue}> {titleValue} </a> </div> diff --git a/src/mui/field/FileField.spec.js b/src/mui/field/FileField.spec.js index b54a751b..50df3982 100644 --- a/src/mui/field/FileField.spec.js +++ b/src/mui/field/FileField.spec.js @@ -9,7 +9,7 @@ describe('<FileField />', () => { }); it('should render a link with correct attributes based on `source` and `title`', () => { - const wrapper = shallow(( + const wrapper = shallow( <FileField record={{ url: 'http://foo.com/bar.jpg', @@ -18,7 +18,7 @@ describe('<FileField />', () => { source="url" title="title" /> - )); + ); const link = wrapper.find('a'); assert.equal(link.prop('href'), 'http://foo.com/bar.jpg'); @@ -26,7 +26,7 @@ describe('<FileField />', () => { }); it('should support deep linking', () => { - const wrapper = shallow(( + const wrapper = shallow( <FileField record={{ file: { @@ -37,7 +37,7 @@ describe('<FileField />', () => { source="file.url" title="file.title" /> - )); + ); const link = wrapper.find('a'); assert.equal(link.prop('href'), 'http://foo.com/bar.jpg'); @@ -45,7 +45,7 @@ describe('<FileField />', () => { }); it('should allow setting static string as title', () => { - const wrapper = shallow(( + const wrapper = shallow( <FileField record={{ url: 'http://foo.com/bar.jpg', @@ -53,29 +53,32 @@ describe('<FileField />', () => { source="url" title="Hello world!" /> - )); + ); const link = wrapper.find('a'); assert.equal(link.prop('title'), 'Hello world!'); }); it('should render a list of links with correct attributes based on `src` and `title`', () => { - const wrapper = shallow(( + const wrapper = shallow( <FileField record={{ - files: [{ - url: 'http://foo.com/bar.jpg', - title: 'Hello world!', - }, { - url: 'http://bar.com/foo.jpg', - title: 'Bye world!', - }], + files: [ + { + url: 'http://foo.com/bar.jpg', + title: 'Hello world!', + }, + { + url: 'http://bar.com/foo.jpg', + title: 'Bye world!', + }, + ], }} source="files" src="url" title="title" /> - )); + ); const links = wrapper.find('a'); assert.equal(links.at(0).prop('href'), 'http://foo.com/bar.jpg'); diff --git a/src/mui/field/FunctionField.js b/src/mui/field/FunctionField.js index e1754d28..773dc8c5 100644 --- a/src/mui/field/FunctionField.js +++ b/src/mui/field/FunctionField.js @@ -6,9 +6,12 @@ import pure from 'recompose/pure'; * @example * <FunctionField source="last_name" label="Name" render={record => `${record.first_name} ${record.last_name}`} /> */ -const FunctionField = ({ record = {}, source, render, elStyle }) => record ? - <span style={elStyle}>{render(record)}</span> : - null; +const FunctionField = ({ record = {}, source, render, elStyle }) => + record + ? <span style={elStyle}> + {render(record)} + </span> + : null; FunctionField.propTypes = { addLabel: PropTypes.bool, diff --git a/src/mui/field/FunctionField.spec.js b/src/mui/field/FunctionField.spec.js index de234db7..f3c01f2a 100644 --- a/src/mui/field/FunctionField.spec.js +++ b/src/mui/field/FunctionField.spec.js @@ -6,8 +6,12 @@ import FunctionField from './FunctionField'; describe('<FunctionField />', () => { it('should render using the render function', () => { const record = { foo: 'bar' }; - const wrapper = shallow(<FunctionField record={record} render={r => r['foo'].substr(0,2)} />); + const wrapper = shallow( + <FunctionField + record={record} + render={r => r['foo'].substr(0, 2)} + /> + ); assert.equal(wrapper.html(), '<span>ba</span>'); }); - }); diff --git a/src/mui/field/ImageField.spec.js b/src/mui/field/ImageField.spec.js index 5b597da7..d1fdb4dc 100644 --- a/src/mui/field/ImageField.spec.js +++ b/src/mui/field/ImageField.spec.js @@ -5,11 +5,14 @@ import ImageField from './ImageField'; describe('<ImageField />', () => { it('should return an empty div when record is not set', () => { - assert.equal(shallow(<ImageField source="url" />).html(), '<div></div>'); + assert.equal( + shallow(<ImageField source="url" />).html(), + '<div></div>' + ); }); it('should render an image with correct attributes based on `source` and `title`', () => { - const wrapper = shallow(( + const wrapper = shallow( <ImageField record={{ url: 'http://foo.com/bar.jpg', @@ -18,7 +21,7 @@ describe('<ImageField />', () => { source="url" title="title" /> - )); + ); const img = wrapper.find('img'); assert.equal(img.prop('src'), 'http://foo.com/bar.jpg'); @@ -27,7 +30,7 @@ describe('<ImageField />', () => { }); it('should support deep linking', () => { - const wrapper = shallow(( + const wrapper = shallow( <ImageField record={{ picture: { @@ -38,7 +41,7 @@ describe('<ImageField />', () => { source="picture.url" title="picture.title" /> - )); + ); const img = wrapper.find('img'); assert.equal(img.prop('src'), 'http://foo.com/bar.jpg'); @@ -47,7 +50,7 @@ describe('<ImageField />', () => { }); it('should allow setting static string as title', () => { - const wrapper = shallow(( + const wrapper = shallow( <ImageField record={{ url: 'http://foo.com/bar.jpg', @@ -55,7 +58,7 @@ describe('<ImageField />', () => { source="url" title="Hello world!" /> - )); + ); const img = wrapper.find('img'); assert.equal(img.prop('alt'), 'Hello world!'); @@ -63,22 +66,25 @@ describe('<ImageField />', () => { }); it('should render a list of images with correct attributes based on `src` and `title`', () => { - const wrapper = shallow(( + const wrapper = shallow( <ImageField record={{ - pictures: [{ - url: 'http://foo.com/bar.jpg', - title: 'Hello world!', - }, { - url: 'http://bar.com/foo.jpg', - title: 'Bye world!', - }], + pictures: [ + { + url: 'http://foo.com/bar.jpg', + title: 'Hello world!', + }, + { + url: 'http://bar.com/foo.jpg', + title: 'Bye world!', + }, + ], }} source="pictures" src="url" title="title" /> - )); + ); const imgs = wrapper.find('img'); assert.equal(imgs.at(0).prop('src'), 'http://foo.com/bar.jpg'); diff --git a/src/mui/field/NumberField.js b/src/mui/field/NumberField.js index ea3ffb8b..9119fdc1 100644 --- a/src/mui/field/NumberField.js +++ b/src/mui/field/NumberField.js @@ -3,7 +3,11 @@ import PropTypes from 'prop-types'; import get from 'lodash.get'; import pure from 'recompose/pure'; -const hasNumberFormat = !!(typeof Intl === 'object' && Intl && typeof Intl.NumberFormat === 'function'); +const hasNumberFormat = !!( + typeof Intl === 'object' && + Intl && + typeof Intl.NumberFormat === 'function' +); /** * Display a numeric value as a locale string. @@ -37,8 +41,17 @@ export const NumberField = ({ record, source, locales, options, elStyle }) => { if (!record) return null; const value = get(record, source); if (value == null) return null; - if (!hasNumberFormat) return <span style={elStyle}>{value}</span>; - return <span style={elStyle}>{value.toLocaleString(locales, options)}</span>; + if (!hasNumberFormat) + return ( + <span style={elStyle}> + {value} + </span> + ); + return ( + <span style={elStyle}> + {value.toLocaleString(locales, options)} + </span> + ); }; NumberField.propTypes = { diff --git a/src/mui/field/NumberField.spec.js b/src/mui/field/NumberField.spec.js index 0cc42564..fb64c9a6 100644 --- a/src/mui/field/NumberField.spec.js +++ b/src/mui/field/NumberField.spec.js @@ -4,38 +4,64 @@ import { shallow } from 'enzyme'; import { NumberField } from './NumberField'; describe('<NumberField />', () => { - it('should return null when the record is not set', () => assert.equal( - shallow(<NumberField source="foo" />).html(), - null, - )); + it('should return null when the record is not set', () => + assert.equal(shallow(<NumberField source="foo" />).html(), null)); - it('should return null when the record has no value for the source', () => assert.equal( - shallow(<NumberField record={{}} source="foo" />).html(), - null, - )); + it('should return null when the record has no value for the source', () => + assert.equal( + shallow(<NumberField record={{}} source="foo" />).html(), + null + )); - it('should render a number', () => assert.equal( - shallow(<NumberField record={{ foo: 1 }} source="foo" />).html(), - '<span>1</span>', - )); + it('should render a number', () => + assert.equal( + shallow(<NumberField record={{ foo: 1 }} source="foo" />).html(), + '<span>1</span>' + )); - it('should pass the options prop to Intl.NumberFormat', () => assert.equal( - shallow(<NumberField record={{ foo: 1 }} source="foo" locales="en-US" options={{ minimumFractionDigits: 2 }} />).html(), - '<span>1.00</span>', - )); + it('should pass the options prop to Intl.NumberFormat', () => + assert.equal( + shallow( + <NumberField + record={{ foo: 1 }} + source="foo" + locales="en-US" + options={{ minimumFractionDigits: 2 }} + /> + ).html(), + '<span>1.00</span>' + )); - it('should use the locales props as an argument to Intl.NumberFormat', () => assert.equal( - shallow(<NumberField record={{ foo: 1 }} source="foo" locales="fr-FR" options={{ minimumFractionDigits: 2 }} />).html(), - '<span>1,00</span>', - )); + it('should use the locales props as an argument to Intl.NumberFormat', () => + assert.equal( + shallow( + <NumberField + record={{ foo: 1 }} + source="foo" + locales="fr-FR" + options={{ minimumFractionDigits: 2 }} + /> + ).html(), + '<span>1,00</span>' + )); - it('should use custom styles passed as an elStyle prop', () => assert.equal( - shallow(<NumberField record={{ foo: 1 }} source="foo" elStyle={{ margin: 1 }} />).html(), - '<span style="margin:1px;">1</span>', - )); + it('should use custom styles passed as an elStyle prop', () => + assert.equal( + shallow( + <NumberField + record={{ foo: 1 }} + source="foo" + elStyle={{ margin: 1 }} + /> + ).html(), + '<span style="margin:1px;">1</span>' + )); - it('should handle deep fields', () => assert.equal( - shallow(<NumberField record={{ foo: { bar: 2 } }} source="foo.bar" />).html(), - '<span>2</span>', - )); + it('should handle deep fields', () => + assert.equal( + shallow( + <NumberField record={{ foo: { bar: 2 } }} source="foo.bar" /> + ).html(), + '<span>2</span>' + )); }); diff --git a/src/mui/field/ReferenceArrayField.js b/src/mui/field/ReferenceArrayField.js index 14d0ef2a..f17e4f64 100644 --- a/src/mui/field/ReferenceArrayField.js +++ b/src/mui/field/ReferenceArrayField.js @@ -55,9 +55,20 @@ export class ReferenceArrayField extends Component { } render() { - const { resource, reference, data, ids, children, basePath, isLoading } = this.props; + const { + resource, + reference, + data, + ids, + children, + basePath, + isLoading, + } = this.props; + if (React.Children.count(children) !== 1) { - throw new Error('<ReferenceArrayField> only accepts a single child (like <Datagrid>)'); + throw new Error( + '<ReferenceArrayField> only accepts a single child (like <Datagrid>)' + ); } if (ids.length !== 0 && Object.keys(data).length !== ids.length) { @@ -100,6 +111,7 @@ const mapStateToProps = (state, props) => { data: getReferencesByIds(state, reference, ids), ids, isLoading: state.admin.loading > 0, + data: getReferencesByIds(state, reference, ids), }; }; diff --git a/src/mui/field/ReferenceField.js b/src/mui/field/ReferenceField.js index 49da561a..38008bd4 100644 --- a/src/mui/field/ReferenceField.js +++ b/src/mui/field/ReferenceField.js @@ -55,7 +55,17 @@ export class ReferenceField extends Component { } render() { - const { record, source, reference, referenceRecord, basePath, allowEmpty, children, elStyle, linkType } = this.props; + const { + record, + source, + reference, + referenceRecord, + basePath, + allowEmpty, + children, + elStyle, + linkType, + } = this.props; if (React.Children.count(children) !== 1) { throw new Error('<ReferenceField> only accepts a single child'); } @@ -63,7 +73,10 @@ export class ReferenceField extends Component { return <LinearProgress />; } const rootPath = basePath.split('/').slice(0, -1).join('/'); - const href = linkToRecord(`${rootPath}/${reference}`, get(record, source)); + const href = linkToRecord( + `${rootPath}/${reference}`, + get(record, source) + ); const child = React.cloneElement(children, { record: referenceRecord, resource: reference, @@ -72,10 +85,18 @@ export class ReferenceField extends Component { translateChoice: false, }); if (linkType === 'edit' || linkType === true) { - return <Link style={elStyle} to={href}>{child}</Link>; + return ( + <Link style={elStyle} to={href}> + {child} + </Link> + ); } if (linkType === 'show') { - return <Link style={elStyle} to={`${href}/show`}>{child}</Link>; + return ( + <Link style={elStyle} to={`${href}/show`}> + {child} + </Link> + ); } return child; } @@ -93,10 +114,8 @@ ReferenceField.propTypes = { reference: PropTypes.string.isRequired, referenceRecord: PropTypes.object, source: PropTypes.string.isRequired, - linkType: PropTypes.oneOfType([ - PropTypes.string, - PropTypes.bool, - ]).isRequired, + linkType: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]) + .isRequired, }; ReferenceField.defaultProps = { @@ -108,7 +127,8 @@ ReferenceField.defaultProps = { function mapStateToProps(state, props) { return { - referenceRecord: state.admin[props.reference].data[get(props.record, props.source)], + referenceRecord: + state.admin[props.reference].data[get(props.record, props.source)], }; } diff --git a/src/mui/field/ReferenceField.spec.js b/src/mui/field/ReferenceField.spec.js index e58682fb..6929c0f5 100644 --- a/src/mui/field/ReferenceField.spec.js +++ b/src/mui/field/ReferenceField.spec.js @@ -20,7 +20,7 @@ describe('<ReferenceField />', () => { > <TextField source="title" /> </ReferenceField>, - { lifecycleExperimental: true }, + { lifecycleExperimental: true } ); assert(crudGetManyAccumulate.calledOnce); }); @@ -37,7 +37,7 @@ describe('<ReferenceField />', () => { > <TextField source="title" /> </ReferenceField>, - { lifecycleExperimental: true }, + { lifecycleExperimental: true } ); assert(crudGetManyAccumulate.notCalled); }); @@ -52,7 +52,7 @@ describe('<ReferenceField />', () => { crudGetManyAccumulate={() => {}} > <TextField source="title" /> - </ReferenceField>, + </ReferenceField> ); const linkElement = wrapper.find('Link'); assert.equal(linkElement.prop('to'), '/bar/123'); @@ -69,7 +69,7 @@ describe('<ReferenceField />', () => { crudGetManyAccumulate={() => {}} > <TextField source="title" /> - </ReferenceField>, + </ReferenceField> ); const linkElement = wrapper.find('Link'); assert.equal(linkElement.prop('to'), '/bar/123/show'); @@ -86,7 +86,7 @@ describe('<ReferenceField />', () => { crudGetManyAccumulate={() => {}} > <TextField source="title" /> - </ReferenceField>, + </ReferenceField> ); const linkElement = wrapper.find('Link'); assert.equal(linkElement.length, 0); diff --git a/src/mui/field/ReferenceManyField.js b/src/mui/field/ReferenceManyField.js index 976cd5b8..62e5b127 100644 --- a/src/mui/field/ReferenceManyField.js +++ b/src/mui/field/ReferenceManyField.js @@ -3,7 +3,11 @@ import PropTypes from 'prop-types'; import { connect } from 'react-redux'; import LinearProgress from 'material-ui/LinearProgress'; import { crudGetManyReference as crudGetManyReferenceAction } from '../../actions/dataActions'; -import { getIds, getReferences, nameRelatedTo } from '../../reducer/references/oneToMany'; +import { + getIds, + getReferences, + nameRelatedTo, +} from '../../reducer/references/oneToMany'; import { SORT_ASC, SORT_DESC } from '../../reducer/resource/list/queryReducer'; /** @@ -68,25 +72,46 @@ export class ReferenceManyField extends Component { } } - setSort = (field) => { - const order = this.state.sort.field === field && this.state.sort.order === SORT_ASC ? SORT_DESC : SORT_ASC; - this.setState( - { sort: { field, order } }, - this.fetchReferences, - ); - } + setSort = field => { + const order = + this.state.sort.field === field && + this.state.sort.order === SORT_ASC + ? SORT_DESC + : SORT_ASC; + this.setState({ sort: { field, order } }, this.fetchReferences); + }; - fetchReferences({ reference, record, resource, target, perPage, filter } = this.props) { + fetchReferences( + { reference, record, resource, target, perPage, filter } = this.props + ) { const { crudGetManyReference } = this.props; const pagination = { page: 1, perPage }; const relatedTo = nameRelatedTo(reference, record.id, resource, target); - crudGetManyReference(reference, target, record.id, relatedTo, pagination, this.state.sort, filter); + crudGetManyReference( + reference, + target, + record.id, + relatedTo, + pagination, + this.state.sort, + filter + ); } render() { - const { resource, reference, data, ids, children, basePath, isLoading } = this.props; + const { + resource, + reference, + data, + ids, + children, + basePath, + isLoading, + } = this.props; if (React.Children.count(children) !== 1) { - throw new Error('<ReferenceManyField> only accepts a single child (like <Datagrid>)'); + throw new Error( + '<ReferenceManyField> only accepts a single child (like <Datagrid>)' + ); } if (typeof ids === 'undefined') { return <LinearProgress style={{ marginTop: '1em' }} />; @@ -134,7 +159,12 @@ ReferenceManyField.defaultProps = { }; function mapStateToProps(state, props) { - const relatedTo = nameRelatedTo(props.reference, props.record.id, props.resource, props.target); + const relatedTo = nameRelatedTo( + props.reference, + props.record.id, + props.resource, + props.target + ); return { data: getReferences(state, props.reference, relatedTo), ids: getIds(state, relatedTo), diff --git a/src/mui/field/ReferenceManyField.spec.js b/src/mui/field/ReferenceManyField.spec.js index b27a12ad..7cb9ffa8 100644 --- a/src/mui/field/ReferenceManyField.spec.js +++ b/src/mui/field/ReferenceManyField.spec.js @@ -82,8 +82,8 @@ describe('<ReferenceManyField />', () => { it('should support record with string identifier', () => { const data = { - "abc-1": { id: "abc-1", title: 'hello' }, - "abc-2": { id: "abc-2", title: 'world' }, + 'abc-1': { id: 'abc-1', title: 'hello' }, + 'abc-2': { id: 'abc-2', title: 'world' }, }; const wrapper = shallow( <ReferenceManyField @@ -106,35 +106,38 @@ describe('<ReferenceManyField />', () => { assert.equal(SingleFieldListElement.length, 1); assert.equal(SingleFieldListElement.at(0).prop('resource'), 'bar'); assert.deepEqual(SingleFieldListElement.at(0).prop('data'), data); - assert.deepEqual(SingleFieldListElement.at(0).prop('ids'), ["abc-1", "abc-2"]); + assert.deepEqual(SingleFieldListElement.at(0).prop('ids'), [ + 'abc-1', + 'abc-2', + ]); }); it('should support record with number identifier', () => { - const data = { - 1: { id: 1, title: 'hello' }, - 2: { id: 2, title: 'world' }, - }; - const wrapper = shallow( - <ReferenceManyField - resource="foo" - reference="bar" - target="foo_id" - basePath="" - data={data} - ids={[1, 2]} - crudGetManyReference={() => {}} - > - <SingleFieldList> - <TextField source="title" /> - </SingleFieldList> - </ReferenceManyField> - ); - const ProgressElements = wrapper.find('LinearProgress'); - assert.equal(ProgressElements.length, 0); - const SingleFieldListElement = wrapper.find('SingleFieldList'); - assert.equal(SingleFieldListElement.length, 1); - assert.equal(SingleFieldListElement.at(0).prop('resource'), 'bar'); - assert.deepEqual(SingleFieldListElement.at(0).prop('data'), data); - assert.deepEqual(SingleFieldListElement.at(0).prop('ids'), [1,2]); - }); + const data = { + 1: { id: 1, title: 'hello' }, + 2: { id: 2, title: 'world' }, + }; + const wrapper = shallow( + <ReferenceManyField + resource="foo" + reference="bar" + target="foo_id" + basePath="" + data={data} + ids={[1, 2]} + crudGetManyReference={() => {}} + > + <SingleFieldList> + <TextField source="title" /> + </SingleFieldList> + </ReferenceManyField> + ); + const ProgressElements = wrapper.find('LinearProgress'); + assert.equal(ProgressElements.length, 0); + const SingleFieldListElement = wrapper.find('SingleFieldList'); + assert.equal(SingleFieldListElement.length, 1); + assert.equal(SingleFieldListElement.at(0).prop('resource'), 'bar'); + assert.deepEqual(SingleFieldListElement.at(0).prop('data'), data); + assert.deepEqual(SingleFieldListElement.at(0).prop('ids'), [1, 2]); + }); }); diff --git a/src/mui/field/RichTextField.js b/src/mui/field/RichTextField.js index 5fcd2bcd..88a6f43b 100644 --- a/src/mui/field/RichTextField.js +++ b/src/mui/field/RichTextField.js @@ -3,15 +3,20 @@ import PropTypes from 'prop-types'; import get from 'lodash.get'; import pure from 'recompose/pure'; -export const removeTags = input => input ? input.replace(/<[^>]+>/gm, '') : ''; +export const removeTags = input => + input ? input.replace(/<[^>]+>/gm, '') : ''; const RichTextField = ({ source, record = {}, stripTags, elStyle }) => { const value = get(record, source); if (stripTags) { - return <div style={elStyle}>{removeTags(value)}</div>; + return ( + <div style={elStyle}> + {removeTags(value)} + </div> + ); } - return <div style={elStyle} dangerouslySetInnerHTML={{ __html: value }}></div>; + return <div style={elStyle} dangerouslySetInnerHTML={{ __html: value }} />; }; RichTextField.propTypes = { diff --git a/src/mui/field/RichTextField.spec.js b/src/mui/field/RichTextField.spec.js index 9f477914..4ab76038 100644 --- a/src/mui/field/RichTextField.spec.js +++ b/src/mui/field/RichTextField.spec.js @@ -10,19 +10,32 @@ describe('stripTags', () => { }); it('should strip HTML tags even with attributes', () => { - assert.equal(removeTags('<a href="http://www.zombo.com">Zombo</a>'), 'Zombo'); - assert.equal(removeTags('<a target="_blank" href="http://www.zombo.com">Zombo</a>'), 'Zombo'); + assert.equal( + removeTags('<a href="http://www.zombo.com">Zombo</a>'), + 'Zombo' + ); + assert.equal( + removeTags( + '<a target="_blank" href="http://www.zombo.com">Zombo</a>' + ), + 'Zombo' + ); }); it('should strip HTML tags splitted on several lines', () => { - assert.equal(removeTags(`<a + assert.equal( + removeTags(`<a href="http://www.zombo.com" - >Zombo</a>`), 'Zombo'); + >Zombo</a>`), + 'Zombo' + ); }); it('should strip HTML embedded tags', () => { assert.equal( - removeTags('<marquee><a href="http://www.zombo.com">Zombo</a></marquee>'), + removeTags( + '<marquee><a href="http://www.zombo.com">Zombo</a></marquee>' + ), 'Zombo' ); }); @@ -44,20 +57,26 @@ describe('<RichTextField />', () => { it('should handle deep fields', () => { const record = { foo: { body: '<h1>Hello world!</h1>' } }; - const wrapper = render(<RichTextField record={record} source="foo.body" />); + const wrapper = render( + <RichTextField record={record} source="foo.body" /> + ); assert.equal(wrapper.html(), '<div><h1>Hello world!</h1></div>'); }); it('should strip HTML tags if stripTags is set to true', () => { const record = { body: '<h1>Hello world!</h1>' }; - const wrapper = render(<RichTextField stripTags={true} record={record} source="body" />); + const wrapper = render( + <RichTextField stripTags={true} record={record} source="body" /> + ); assert.equal(wrapper.html(), '<div>Hello world!</div>'); }); it('should not strip HTML tags if stripTags is set to false', () => { const record = { body: '<h1>Hello world!</h1>' }; - const wrapper = render(<RichTextField stripTags={false} record={record} source="body" />); + const wrapper = render( + <RichTextField stripTags={false} record={record} source="body" /> + ); assert.equal(wrapper.html(), '<div><h1>Hello world!</h1></div>'); }); diff --git a/src/mui/field/SelectField.js b/src/mui/field/SelectField.js index e99c78cc..adde6c7c 100644 --- a/src/mui/field/SelectField.js +++ b/src/mui/field/SelectField.js @@ -64,19 +64,29 @@ import translate from '../../i18n/translate'; * * **Tip**: <ReferenceField> sets `translateChoice` to false by default. */ -export const SelectField = ({ source, record, choices, elStyle, optionValue, optionText, translate, translateChoice }) => { +export const SelectField = ({ + source, + record, + choices, + elStyle, + optionValue, + optionText, + translate, + translateChoice, +}) => { const value = get(record, source); const choice = choices.find(c => c[optionValue] === value); if (!choice) return null; - const choiceName = React.isValidElement(optionText) ? // eslint-disable-line no-nested-ternary - React.cloneElement(optionText, { record: choice }) : - (typeof optionText === 'function' ? - optionText(choice) : - choice[optionText] - ); + const choiceName = React.isValidElement(optionText) // eslint-disable-line no-nested-ternary + ? React.cloneElement(optionText, { record: choice }) + : typeof optionText === 'function' + ? optionText(choice) + : choice[optionText]; return ( <span style={elStyle}> - {translateChoice ? translate(choiceName, { _: choiceName }) : choiceName} + {translateChoice + ? translate(choiceName, { _: choiceName }) + : choiceName} </span> ); }; @@ -106,10 +116,7 @@ SelectField.defaultProps = { translateChoice: true, }; -const enhance = compose( - pure, - translate, -); +const enhance = compose(pure, translate); const EnhancedSelectField = enhance(SelectField); diff --git a/src/mui/field/SelectField.spec.js b/src/mui/field/SelectField.spec.js index 196b125c..b039b20f 100644 --- a/src/mui/field/SelectField.spec.js +++ b/src/mui/field/SelectField.spec.js @@ -6,124 +6,139 @@ import { SelectField } from './SelectField'; describe('<SelectField />', () => { const defaultProps = { source: 'foo', - choices: [ - { id: 0, name: 'hello' }, - { id: 1, name: 'world' }, - ], + choices: [{ id: 0, name: 'hello' }, { id: 1, name: 'world' }], translate: x => x, }; - it('should return null when the record is not set', () => assert.equal( - shallow(<SelectField {...defaultProps} />).html(), - null, - )); + it('should return null when the record is not set', () => + assert.equal(shallow(<SelectField {...defaultProps} />).html(), null)); - it('should return null when the record has no value for the source', () => assert.equal( - shallow(<SelectField {...defaultProps} record={{}} />).html(), - null, - )); + it('should return null when the record has no value for the source', () => + assert.equal( + shallow(<SelectField {...defaultProps} record={{}} />).html(), + null + )); - it('should return null when the record has a value for the source not in the choices', () => assert.equal( - shallow(<SelectField {...defaultProps} record={{ foo: 2 }} />).html(), - null, - )); + it('should return null when the record has a value for the source not in the choices', () => + assert.equal( + shallow( + <SelectField {...defaultProps} record={{ foo: 2 }} /> + ).html(), + null + )); it('should render the choice', () => { - const wrapper = shallow(<SelectField {...defaultProps} record={{ foo: 0 }} />); + const wrapper = shallow( + <SelectField {...defaultProps} record={{ foo: 0 }} /> + ); const chipElement = wrapper.find('span'); assert.equal(chipElement.children().text(), 'hello'); }); it('should use custom styles passed as an elStyle prop', () => { - const wrapper = shallow(<SelectField - {...defaultProps} - record={{ foo: 1 }} - elStyle={{ margin: 1 }} - />); + const wrapper = shallow( + <SelectField + {...defaultProps} + record={{ foo: 1 }} + elStyle={{ margin: 1 }} + /> + ); const chipElement = wrapper.find('span'); assert.deepEqual(chipElement.prop('style'), { margin: 1 }); }); it('should handle deep fields', () => { - const wrapper = shallow(<SelectField - {...defaultProps} - record={{ foo: { bar: 0 } }} - source="foo.bar" - />); + const wrapper = shallow( + <SelectField + {...defaultProps} + record={{ foo: { bar: 0 } }} + source="foo.bar" + /> + ); const chipElement = wrapper.find('span'); assert.equal(chipElement.children().text(), 'hello'); }); it('should use optionValue as value identifier', () => { - const wrapper = shallow(<SelectField - {...defaultProps} - record={{ foo: 0 }} - optionValue="foobar" - choices={[ - { foobar: 0, name: 'hello' }, - ]} - />); + const wrapper = shallow( + <SelectField + {...defaultProps} + record={{ foo: 0 }} + optionValue="foobar" + choices={[{ foobar: 0, name: 'hello' }]} + /> + ); const chipElement = wrapper.find('span'); assert.equal(chipElement.children().text(), 'hello'); }); it('should use optionText with a string value as text identifier', () => { - const wrapper = shallow(<SelectField - {...defaultProps} - record={{ foo: 0 }} - optionText="foobar" - choices={[ - { id: 0, foobar: 'hello' }, - ]} - />); + const wrapper = shallow( + <SelectField + {...defaultProps} + record={{ foo: 0 }} + optionText="foobar" + choices={[{ id: 0, foobar: 'hello' }]} + /> + ); const chipElement = wrapper.find('span'); assert.equal(chipElement.children().text(), 'hello'); }); it('should use optionText with a function value as text identifier', () => { - const wrapper = shallow(<SelectField - {...defaultProps} - record={{ foo: 0 }} - optionText={choice => choice.foobar} - choices={[ - { id: 0, foobar: 'hello' }, - ]} - />); + const wrapper = shallow( + <SelectField + {...defaultProps} + record={{ foo: 0 }} + optionText={choice => choice.foobar} + choices={[{ id: 0, foobar: 'hello' }]} + /> + ); const chipElement = wrapper.find('span'); assert.equal(chipElement.children().text(), 'hello'); }); it('should use optionText with an element value as text identifier', () => { - const Foobar = ({ record }) => <span>{record.foobar}</span>; - const wrapper = shallow(<SelectField - {...defaultProps} - record={{ foo: 0 }} - optionText={<Foobar />} - choices={[ - { id: 0, foobar: 'hello' }, - ]} - />); + const Foobar = ({ record }) => + <span> + {record.foobar} + </span>; + const wrapper = shallow( + <SelectField + {...defaultProps} + record={{ foo: 0 }} + optionText={<Foobar />} + choices={[{ id: 0, foobar: 'hello' }]} + /> + ); const chipElement = wrapper.find('Foobar'); - assert.deepEqual(chipElement.prop('record'), { id: 0, foobar: 'hello' }); + assert.deepEqual(chipElement.prop('record'), { + id: 0, + foobar: 'hello', + }); }); it('should translate the choice by default', () => { - const wrapper = shallow(<SelectField - {...defaultProps} - record={{ foo: 0 }} - translate={x => `**${x}**`} - />); + const wrapper = shallow( + <SelectField + {...defaultProps} + record={{ foo: 0 }} + translate={x => `**${x}**`} + /> + ); const chipElement = wrapper.find('span'); assert.equal(chipElement.children().text(), '**hello**'); }); it('should not translate the choice if translateChoice is false', () => { - const wrapper = shallow(<SelectField - {...defaultProps} - record={{ foo: 0 }} - translate={x => `**${x}**`} - translateChoice={false} - />); + const wrapper = shallow( + <SelectField + {...defaultProps} + record={{ foo: 0 }} + translate={x => `**${x}**`} + translateChoice={false} + /> + ); const chipElement = wrapper.find('span'); assert.equal(chipElement.children().text(), 'hello'); }); diff --git a/src/mui/field/TextField.spec.js b/src/mui/field/TextField.spec.js index e2b5d45e..277b5661 100644 --- a/src/mui/field/TextField.spec.js +++ b/src/mui/field/TextField.spec.js @@ -5,14 +5,26 @@ import TextField from './TextField'; describe('<TextField />', () => { it('should display record specific value as plain text', () => { - const record = { title: "I'm sorry, Dave. I'm afraid I can't do that." }; + const record = { + title: "I'm sorry, Dave. I'm afraid I can't do that.", + }; const wrapper = shallow(<TextField record={record} source="title" />); - assert.equal(wrapper.html(), '<span>I'm sorry, Dave. I'm afraid I can't do that.</span>'); + assert.equal( + wrapper.html(), + '<span>I'm sorry, Dave. I'm afraid I can't do that.</span>' + ); }); it('should handle deep fields', () => { - const record = { foo: { title: "I'm sorry, Dave. I'm afraid I can't do that." } }; - const wrapper = shallow(<TextField record={record} source="foo.title" />); - assert.equal(wrapper.html(), '<span>I'm sorry, Dave. I'm afraid I can't do that.</span>'); + const record = { + foo: { title: "I'm sorry, Dave. I'm afraid I can't do that." }, + }; + const wrapper = shallow( + <TextField record={record} source="foo.title" /> + ); + assert.equal( + wrapper.html(), + '<span>I'm sorry, Dave. I'm afraid I can't do that.</span>' + ); }); }); diff --git a/src/mui/field/UrlField.js b/src/mui/field/UrlField.js index e69139be..36cdd0ba 100644 --- a/src/mui/field/UrlField.js +++ b/src/mui/field/UrlField.js @@ -3,11 +3,10 @@ import PropTypes from 'prop-types'; import get from 'lodash.get'; import pure from 'recompose/pure'; -const UrlField = ({ source, record = {}, elStyle }) => ( +const UrlField = ({ source, record = {}, elStyle }) => <a href={get(record, source)} style={elStyle}> {get(record, source)} - </a> -); + </a>; UrlField.propTypes = { addLabel: PropTypes.bool, diff --git a/src/mui/field/UrlField.spec.js b/src/mui/field/UrlField.spec.js index cb77b522..7a606963 100644 --- a/src/mui/field/UrlField.spec.js +++ b/src/mui/field/UrlField.spec.js @@ -7,12 +7,22 @@ describe('<UrlField />', () => { it('should display a link', () => { const record = { website: 'https://en.wikipedia.org/wiki/HAL_9000' }; const wrapper = shallow(<UrlField record={record} source="website" />); - assert.equal(wrapper.html(), '<a href="https://en.wikipedia.org/wiki/HAL_9000">https://en.wikipedia.org/wiki/HAL_9000</a>'); + assert.equal( + wrapper.html(), + '<a href="https://en.wikipedia.org/wiki/HAL_9000">https://en.wikipedia.org/wiki/HAL_9000</a>' + ); }); it('should handle deep fields', () => { - const record = { foo: { website: 'https://en.wikipedia.org/wiki/HAL_9000' } }; - const wrapper = shallow(<UrlField record={record} source="foo.website" />); - assert.equal(wrapper.html(), '<a href="https://en.wikipedia.org/wiki/HAL_9000">https://en.wikipedia.org/wiki/HAL_9000</a>'); + const record = { + foo: { website: 'https://en.wikipedia.org/wiki/HAL_9000' }, + }; + const wrapper = shallow( + <UrlField record={record} source="foo.website" /> + ); + assert.equal( + wrapper.html(), + '<a href="https://en.wikipedia.org/wiki/HAL_9000">https://en.wikipedia.org/wiki/HAL_9000</a>' + ); }); }); diff --git a/src/mui/form/FormField.js b/src/mui/form/FormField.js index 93c46ce4..e13c68d5 100644 --- a/src/mui/form/FormField.js +++ b/src/mui/form/FormField.js @@ -4,7 +4,7 @@ import { Field } from 'redux-form'; import Labeled from '../input/Labeled'; import { required } from './validate'; -const isRequired = (validate) => { +const isRequired = validate => { if (validate === required) return true; if (Array.isArray(validate)) { return validate.includes(required); @@ -24,7 +24,7 @@ const FormField = ({ input, ...rest }) => { label={input.props.label} isRequired={isRequired(input.props.validate)} > - { input } + {input} </Field> ); } @@ -50,7 +50,9 @@ const FormField = ({ input, ...rest }) => { </Labeled> ); } - return (typeof input.type === 'string') ? input : React.cloneElement(input, rest); + return typeof input.type === 'string' + ? input + : React.cloneElement(input, rest); }; export default FormField; diff --git a/src/mui/form/FormField.spec.js b/src/mui/form/FormField.spec.js index 4db5861e..7e45b539 100644 --- a/src/mui/form/FormField.spec.js +++ b/src/mui/form/FormField.spec.js @@ -12,7 +12,7 @@ describe('<FormField>', () => { const component = wrapper.find('Foo'); assert.equal(component.length, 1); }); - it('should render the input component even if it\'s an html element', () => { + it("should render the input component even if it's an html element", () => { const wrapper = shallow(<FormField input={<div />} />); const component = wrapper.find('div'); assert.equal(component.length, 1); @@ -33,7 +33,9 @@ describe('<FormField>', () => { assert.equal(component, 'Foo'); }); it('should render a <Labeled /> component when addField is true and addLabel is true', () => { - const wrapper = shallow(<FormField input={<Foo addField addLabel />} />); + const wrapper = shallow( + <FormField input={<Foo addField addLabel />} /> + ); const component = wrapper.find('Field').prop('component').name; assert.equal(component, 'Labeled'); }); diff --git a/src/mui/form/FormTab.js b/src/mui/form/FormTab.js index 8c1c0415..972da21e 100644 --- a/src/mui/form/FormTab.js +++ b/src/mui/form/FormTab.js @@ -1,12 +1,20 @@ import React from 'react'; import FormField from './FormField'; -const FormTab = ({ label, icon, children, ...rest }) => <span> - {React.Children.map(children, input => input && ( - <div key={input.props.source} style={input.props.style} className={`aor-input-${input.props.source}`}> - <FormField input={input} {...rest} /> - </div> - ))} -</span>; +const FormTab = ({ label, icon, children, ...rest }) => + <span> + {React.Children.map( + children, + input => + input && + <div + key={input.props.source} + style={input.props.style} + className={`aor-input-${input.props.source}`} + > + <FormField input={input} {...rest} /> + </div> + )} + </span>; export default FormTab; diff --git a/src/mui/form/SimpleForm.js b/src/mui/form/SimpleForm.js index 8dfcee4d..22d52c78 100644 --- a/src/mui/form/SimpleForm.js +++ b/src/mui/form/SimpleForm.js @@ -10,24 +10,46 @@ import Toolbar from './Toolbar'; const formStyle = { padding: '0 1em 1em 1em' }; export class SimpleForm extends Component { - handleSubmitWithRedirect = (redirect = this.props.redirect) => this.props.handleSubmit(values => this.props.save(values, redirect)); + handleSubmitWithRedirect = (redirect = this.props.redirect) => + this.props.handleSubmit(values => this.props.save(values, redirect)); render() { - const { children, invalid, record, resource, basePath, submitOnEnter, toolbar } = this.props; + const { + children, + invalid, + record, + resource, + basePath, + submitOnEnter, + toolbar, + } = this.props; return ( <form className="simple-form"> <div style={formStyle}> - {Children.map(children, input => input && ( - <div key={input.props.source} className={`aor-input-${input.props.source}`} style={input.props.style}> - <FormField input={input} resource={resource} record={record} basePath={basePath} /> - </div> - ))} + {Children.map( + children, + input => + input && + <div + key={input.props.source} + className={`aor-input-${input.props.source}`} + style={input.props.style} + > + <FormField + input={input} + resource={resource} + record={record} + basePath={basePath} + /> + </div> + )} </div> - {toolbar && React.cloneElement(toolbar, { - handleSubmitWithRedirect: this.handleSubmitWithRedirect, - invalid, - submitOnEnter, - })} + {toolbar && + React.cloneElement(toolbar, { + handleSubmitWithRedirect: this.handleSubmitWithRedirect, + invalid, + submitOnEnter, + })} </form> ); } @@ -36,18 +58,12 @@ export class SimpleForm extends Component { SimpleForm.propTypes = { basePath: PropTypes.string, children: PropTypes.node, - defaultValue: PropTypes.oneOfType([ - PropTypes.object, - PropTypes.func, - ]), + defaultValue: PropTypes.oneOfType([PropTypes.object, PropTypes.func]), handleSubmit: PropTypes.func, // passed by redux-form invalid: PropTypes.bool, record: PropTypes.object, resource: PropTypes.string, - redirect: PropTypes.oneOfType([ - PropTypes.string, - PropTypes.bool, - ]), + redirect: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]), save: PropTypes.func, // the handler defined in the parent, which triggers the REST submission submitOnEnter: PropTypes.bool, toolbar: PropTypes.element, @@ -66,7 +82,7 @@ const enhance = compose( reduxForm({ form: 'record-form', enableReinitialize: true, - }), + }) ); export default enhance(SimpleForm); diff --git a/src/mui/form/SimpleForm.spec.js b/src/mui/form/SimpleForm.spec.js index ddad9ac2..dc3168f8 100644 --- a/src/mui/form/SimpleForm.spec.js +++ b/src/mui/form/SimpleForm.spec.js @@ -14,7 +14,10 @@ describe('<SimpleForm />', () => { </SimpleForm> ); const inputs = wrapper.find('FormField'); - assert.deepEqual(inputs.map(i => i.prop('input').props.source), ['name', 'city']); + assert.deepEqual(inputs.map(i => i.prop('input').props.source), [ + 'name', + 'city', + ]); }); it('should display <Toolbar />', () => { diff --git a/src/mui/form/TabbedForm.js b/src/mui/form/TabbedForm.js index 12f37f19..cc42e901 100644 --- a/src/mui/form/TabbedForm.js +++ b/src/mui/form/TabbedForm.js @@ -1,6 +1,11 @@ import React, { Children, Component } from 'react'; import PropTypes from 'prop-types'; -import { reduxForm, getFormAsyncErrors, getFormSyncErrors, getFormSubmitErrors } from 'redux-form'; +import { + reduxForm, + getFormAsyncErrors, + getFormSyncErrors, + getFormSubmitErrors, +} from 'redux-form'; import { connect } from 'react-redux'; import compose from 'recompose/compose'; import { Tabs, Tab } from 'material-ui/Tabs'; @@ -23,15 +28,30 @@ export class TabbedForm extends Component { }; } - handleChange = (value) => { + handleChange = value => { this.setState({ value }); }; - handleSubmitWithRedirect = (redirect = this.props.redirect) => this.props.handleSubmit(values => this.props.save(values, redirect)); + handleSubmitWithRedirect = (redirect = this.props.redirect) => + this.props.handleSubmit(values => this.props.save(values, redirect)); render() { - const { basePath, children, contentContainerStyle, invalid, muiTheme, record, resource, submitOnEnter, tabsWithErrors, toolbar, translate } = this.props; + const { + basePath, + children, + contentContainerStyle, + invalid, + muiTheme, + record, + resource, + submitOnEnter, + tabsWithErrors, + toolbar, + translate, + } = this.props; + const styles = getStyles(muiTheme); + return ( <form className="tabbed-form"> <div style={styles.form}> @@ -40,25 +60,37 @@ export class TabbedForm extends Component { onChange={this.handleChange} contentContainerStyle={contentContainerStyle} > - {React.Children.map(children, (tab, index) => + {React.Children.map(children, (tab, index) => ( <Tab key={tab.props.label} className="form-tab" - label={translate(tab.props.label, { _: tab.props.label })} + label={translate(tab.props.label, { + _: tab.props.label, + })} value={index} icon={tab.props.icon} - buttonStyle={tabsWithErrors.includes(tab.props.label) && this.state.value !== index ? styles.errorTabButton : null} + buttonStyle={ + tabsWithErrors.includes(tab.props.label) && + this.state.value !== index ? ( + styles.errorTabButton + ) : null + } > - {React.cloneElement(tab, { resource, record, basePath })} - </Tab>, - )} + {React.cloneElement(tab, { + resource, + record, + basePath, + })} + </Tab> + ))} </Tabs> </div> - {toolbar && React.cloneElement(toolbar, { - handleSubmitWithRedirect: this.handleSubmitWithRedirect, - invalid, - submitOnEnter, - })} + {toolbar && + React.cloneElement(toolbar, { + handleSubmitWithRedirect: this.handleSubmitWithRedirect, + invalid, + submitOnEnter, + })} </form> ); } @@ -68,18 +100,12 @@ TabbedForm.propTypes = { basePath: PropTypes.string, children: PropTypes.node, contentContainerStyle: PropTypes.object, - defaultValue: PropTypes.oneOfType([ - PropTypes.object, - PropTypes.func, - ]), + defaultValue: PropTypes.oneOfType([PropTypes.object, PropTypes.func]), handleSubmit: PropTypes.func, // passed by redux-form invalid: PropTypes.bool, muiTheme: PropTypes.object.isRequired, record: PropTypes.object, - redirect: PropTypes.oneOfType([ - PropTypes.string, - PropTypes.bool, - ]), + redirect: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]), resource: PropTypes.string, save: PropTypes.func, // the handler defined in the parent, which triggers the REST submission submitOnEnter: PropTypes.bool, @@ -95,7 +121,7 @@ TabbedForm.defaultProps = { toolbar: <Toolbar />, }; -const collectErrors = (state) => { +const collectErrors = state => { const syncErrors = getFormSyncErrors('record-form')(state); const asyncErrors = getFormAsyncErrors('record-form')(state); const submitErrors = getFormSubmitErrors('record-form')(state); @@ -107,7 +133,11 @@ const collectErrors = (state) => { }; }; -export const findTabsWithErrors = (state, props, collectErrorsImpl = collectErrors) => { +export const findTabsWithErrors = ( + state, + props, + collectErrorsImpl = collectErrors +) => { const errors = collectErrorsImpl(state); return Children.toArray(props.children).reduce((acc, child) => { @@ -123,10 +153,10 @@ export const findTabsWithErrors = (state, props, collectErrorsImpl = collectErro const enhance = compose( connect((state, props) => { - const children = Children.toArray(props.children).reduce((acc, child) => [ - ...acc, - ...Children.toArray(child.props.children), - ], []); + const children = Children.toArray(props.children).reduce( + (acc, child) => [...acc, ...Children.toArray(child.props.children)], + [] + ); return { tabsWithErrors: findTabsWithErrors(state, props), @@ -137,7 +167,7 @@ const enhance = compose( form: 'record-form', enableReinitialize: true, }), - muiThemeable(), + muiThemeable() ); export default enhance(TabbedForm); diff --git a/src/mui/form/TabbedForm.spec.js b/src/mui/form/TabbedForm.spec.js index a0c11df7..d44a8fdc 100644 --- a/src/mui/form/TabbedForm.spec.js +++ b/src/mui/form/TabbedForm.spec.js @@ -11,10 +11,14 @@ const muiTheme = { textField: { errorColor: 'red' } }; describe('<TabbedForm />', () => { it('should render tabs', () => { const wrapper = shallow( - <TabbedForm translate={translate} muiTheme={muiTheme} tabsWithErrors={[]}> + <TabbedForm + translate={translate} + muiTheme={muiTheme} + tabsWithErrors={[]} + > <FormTab /> <FormTab /> - </TabbedForm>, + </TabbedForm> ); const tabsContainer = wrapper.find('Tabs'); assert.equal(tabsContainer.length, 1); @@ -24,7 +28,11 @@ describe('<TabbedForm />', () => { it('should display <Toolbar />', () => { const wrapper = shallow( - <TabbedForm translate={translate} muiTheme={muiTheme} tabsWithErrors={[]} />, + <TabbedForm + translate={translate} + muiTheme={muiTheme} + tabsWithErrors={[]} + /> ); const button = wrapper.find('Toolbar'); assert.equal(button.length, 1); @@ -33,13 +41,25 @@ describe('<TabbedForm />', () => { it('should pass submitOnEnter to <Toolbar />', () => { const handleSubmit = () => {}; const wrapper1 = shallow( - <TabbedForm translate={translate} submitOnEnter={false} handleSubmit={handleSubmit} muiTheme={muiTheme} tabsWithErrors={[]} />, + <TabbedForm + translate={translate} + submitOnEnter={false} + handleSubmit={handleSubmit} + muiTheme={muiTheme} + tabsWithErrors={[]} + /> ); const button1 = wrapper1.find('Toolbar'); assert.equal(button1.prop('submitOnEnter'), false); const wrapper2 = shallow( - <TabbedForm translate={translate} submitOnEnter handleSubmit={handleSubmit} muiTheme={muiTheme} tabsWithErrors={[]} />, + <TabbedForm + translate={translate} + submitOnEnter + handleSubmit={handleSubmit} + muiTheme={muiTheme} + tabsWithErrors={[]} + /> ); const button2 = wrapper2.find('Toolbar'); assert.strictEqual(button2.prop('submitOnEnter'), true); @@ -47,25 +67,35 @@ describe('<TabbedForm />', () => { it('should set the style of an inactive Tab button with errors', () => { const wrapper = shallow( - <TabbedForm translate={translate} muiTheme={muiTheme} tabsWithErrors={['tab2']}> + <TabbedForm + translate={translate} + muiTheme={muiTheme} + tabsWithErrors={['tab2']} + > <FormTab label="tab1" /> <FormTab label="tab2" /> - </TabbedForm>, + </TabbedForm> ); const tabs = wrapper.find('Tab'); const tab1 = tabs.at(0); const tab2 = tabs.at(1); assert.deepEqual(tab1.prop('buttonStyle'), null); - assert.deepEqual(tab2.prop('buttonStyle'), { color: muiTheme.textField.errorColor }); + assert.deepEqual(tab2.prop('buttonStyle'), { + color: muiTheme.textField.errorColor, + }); }); it('should not set the style of an active Tab button with errors', () => { const wrapper = shallow( - <TabbedForm translate={translate} muiTheme={muiTheme} tabsWithErrors={['tab1']}> + <TabbedForm + translate={translate} + muiTheme={muiTheme} + tabsWithErrors={['tab1']} + > <FormTab label="tab1" /> <FormTab label="tab2" /> - </TabbedForm>, + </TabbedForm> ); const tabs = wrapper.find('Tab'); const tab1 = tabs.at(0); @@ -77,7 +107,10 @@ describe('<TabbedForm />', () => { describe('findTabsWithErrors', () => { it('should find the tabs containing errors', () => { - const collectErrors = () => ({ field1: 'required', field5: 'required' }); + const collectErrors = () => ({ + field1: 'required', + field5: 'required', + }); const state = {}; const props = { children: [ @@ -85,19 +118,19 @@ describe('<TabbedForm />', () => { FormTab, { label: 'tab1' }, createElement('input', { source: 'field1' }), - createElement('input', { source: 'field2' }), + createElement('input', { source: 'field2' }) ), createElement( FormTab, { label: 'tab2' }, createElement('input', { source: 'field3' }), - createElement('input', { source: 'field4' }), + createElement('input', { source: 'field4' }) ), createElement( FormTab, { label: 'tab3' }, createElement('input', { source: 'field5' }), - createElement('input', { source: 'field6' }), + createElement('input', { source: 'field6' }) ), ], }; diff --git a/src/mui/form/getDefaultValues.js b/src/mui/form/getDefaultValues.js index 845dc7a6..74af4b8d 100644 --- a/src/mui/form/getDefaultValues.js +++ b/src/mui/form/getDefaultValues.js @@ -3,15 +3,21 @@ import { createSelector } from 'reselect'; import set from 'lodash.set'; const getDefaultValues = (children, data = {}, defaultValue = {}) => { - const globalDefaultValue = typeof defaultValue === 'function' ? defaultValue() : defaultValue; + const globalDefaultValue = + typeof defaultValue === 'function' ? defaultValue() : defaultValue; const defaultValueFromChildren = children - .map(child => ({ source: child.props.source, defaultValue: child.props.defaultValue })) + .map(child => ({ + source: child.props.source, + defaultValue: child.props.defaultValue, + })) .reduce((prev, next) => { if (next.defaultValue != null) { set( prev, next.source, - typeof next.defaultValue === 'function' ? next.defaultValue() : next.defaultValue, + typeof next.defaultValue === 'function' + ? next.defaultValue() + : next.defaultValue ); } return prev; @@ -23,8 +29,10 @@ const getChildren = (state, props) => props.children; const getRecord = (state, props) => props.record; const getDefaultValue = (state, props) => props.defaultValue; - export default createSelector( - getChildren, getRecord, getDefaultValue, - (children, record, defaultValue) => getDefaultValues(Children.toArray(children), record, defaultValue) + getChildren, + getRecord, + getDefaultValue, + (children, record, defaultValue) => + getDefaultValues(Children.toArray(children), record, defaultValue) ); diff --git a/src/mui/form/getDefaultValues.spec.js b/src/mui/form/getDefaultValues.spec.js index 860638ad..004524ba 100644 --- a/src/mui/form/getDefaultValues.spec.js +++ b/src/mui/form/getDefaultValues.spec.js @@ -7,8 +7,14 @@ describe('getDefaultValues', () => { const someTitle = 'some value'; const formElements = { children: [ - createElement('input', { defaultValue: someTitle, source: 'title' }), - createElement('input', { defaultValue: someTitle, source: 'nested.title' }), + createElement('input', { + defaultValue: someTitle, + source: 'title', + }), + createElement('input', { + defaultValue: someTitle, + source: 'nested.title', + }), ], }; const expectedResult = { diff --git a/src/mui/form/validate.js b/src/mui/form/validate.js index d2ce108c..fbb00ad6 100644 --- a/src/mui/form/validate.js +++ b/src/mui/form/validate.js @@ -2,29 +2,45 @@ /* @link http://stackoverflow.com/questions/46155/validate-email-address-in-javascript */ const EMAIL_REGEX = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; -const isEmpty = value => (typeof value === 'undefined' || value === null || value === ''); +const isEmpty = value => + typeof value === 'undefined' || value === null || value === ''; -export const required = (value, _, props) => isEmpty(value) ? props.translate('aor.validation.required') : undefined; +export const required = (value, _, props) => + isEmpty(value) ? props.translate('aor.validation.required') : undefined; export const minLength = (min, message) => (value, _, props) => - !isEmpty(value) && value.length < min ? props.translate(message || 'aor.validation.minLength', { min }) : undefined; + !isEmpty(value) && value.length < min + ? props.translate(message || 'aor.validation.minLength', { min }) + : undefined; export const maxLength = (max, message) => (value, _, props) => - !isEmpty(value) && value.length > max ? props.translate(message || 'aor.validation.maxLength', { max }) : undefined; + !isEmpty(value) && value.length > max + ? props.translate(message || 'aor.validation.maxLength', { max }) + : undefined; export const minValue = (min, message) => (value, _, props) => - !isEmpty(value) && value < min ? props.translate(message || 'aor.validation.minValue', { min }) : undefined; + !isEmpty(value) && value < min + ? props.translate(message || 'aor.validation.minValue', { min }) + : undefined; export const maxValue = (max, message) => (value, _, props) => - !isEmpty(value) && value > max ? props.translate(message || 'aor.validation.maxValue', { max }) : undefined; + !isEmpty(value) && value > max + ? props.translate(message || 'aor.validation.maxValue', { max }) + : undefined; export const number = (value, _, props) => - !isEmpty(value) && isNaN(Number(value)) ? props.translate('aor.validation.number') : undefined; + !isEmpty(value) && isNaN(Number(value)) + ? props.translate('aor.validation.number') + : undefined; export const regex = (pattern, message) => (value, _, props) => - !isEmpty(value) && typeof value === 'string' && !pattern.test(value) ? props.translate(message) : undefined; + !isEmpty(value) && typeof value === 'string' && !pattern.test(value) + ? props.translate(message) + : undefined; export const email = regex(EMAIL_REGEX, 'aor.validation.email'); export const choices = (list, message) => (value, _, props) => - !isEmpty(value) && list.indexOf(value) === -1 ? props.translate(message) : undefined; + !isEmpty(value) && list.indexOf(value) === -1 + ? props.translate(message) + : undefined; diff --git a/src/mui/form/validate.spec.js b/src/mui/form/validate.spec.js index a9d9bac3..f137453f 100644 --- a/src/mui/form/validate.spec.js +++ b/src/mui/form/validate.spec.js @@ -1,14 +1,23 @@ import assert from 'assert'; -import { required, minLength, maxLength, minValue, maxValue, number, regex, email, choices } from './validate'; +import { + required, + minLength, + maxLength, + minValue, + maxValue, + number, + regex, + email, + choices, +} from './validate'; describe('Validators', () => { const test = (validator, inputs, message) => assert.deepEqual( inputs .map(input => validator(input, null, { translate: x => x })) - .filter(m => m === message) - , - Array(...Array(inputs.length)).map(() => message), + .filter(m => m === message), + Array(...Array(inputs.length)).map(() => message) ); describe('required', () => { @@ -94,10 +103,18 @@ describe('Validators', () => { test(regex(/foo/, 'not foo'), [1234, new Date()], undefined); }); it('should return undefined if the value matches the pattern', () => { - test(regex(/foo/, 'not foo'), ['foobar', 'barfoo', 'barfoobar', 'foofoo'], undefined); + test( + regex(/foo/, 'not foo'), + ['foobar', 'barfoo', 'barfoobar', 'foofoo'], + undefined + ); }); it('should return an error message if the value does not match the pattern', () => { - test(regex(/foo/, 'not foo'), ['bar', 'barfo', 'hello, world'], 'not foo'); + test( + regex(/foo/, 'not foo'), + ['bar', 'barfo', 'hello, world'], + 'not foo' + ); }); }); describe('email', () => { diff --git a/src/mui/input/AutocompleteInput.js b/src/mui/input/AutocompleteInput.js index 16dd3d0a..aa977a90 100644 --- a/src/mui/input/AutocompleteInput.js +++ b/src/mui/input/AutocompleteInput.js @@ -78,12 +78,22 @@ export class AutocompleteInput extends Component { const { choices, input, optionValue } = this.props; input.onChange(choices[index][optionValue]); } - } + }; getSuggestion(choice) { - const { optionText, optionValue, translate, translateChoice } = this.props; - const choiceName = typeof optionText === 'function' ? optionText(choice) : get(choice, optionText); - return translateChoice ? translate(choiceName, { _: choiceName }) : choiceName; + const { + optionText, + optionValue, + translate, + translateChoice, + } = this.props; + const choiceName = + typeof optionText === 'function' + ? optionText(choice) + : get(choice, optionText); + return translateChoice + ? translate(choiceName, { _: choiceName }) + : choiceName; } render() { @@ -106,16 +116,27 @@ export class AutocompleteInput extends Component { } const { touched, error } = meta; - const selectedSource = choices.find(choice => get(choice, optionValue) === input.value); + const selectedSource = choices.find( + choice => get(choice, optionValue) === input.value + ); const dataSource = choices.map(choice => ({ value: get(choice, optionValue), text: this.getSuggestion(choice), })); return ( <AutoComplete - searchText={selectedSource && this.getSuggestion(selectedSource)} + searchText={ + selectedSource && this.getSuggestion(selectedSource) + } dataSource={dataSource} - floatingLabelText={<FieldTitle label={label} source={source} resource={resource} isRequired={isRequired} />} + floatingLabelText={ + <FieldTitle + label={label} + source={source} + resource={resource} + isRequired={isRequired} + /> + } filter={filter} onNewRequest={this.handleNewRequest} onUpdateInput={setFilter} @@ -139,10 +160,8 @@ AutocompleteInput.propTypes = { meta: PropTypes.object, options: PropTypes.object, optionElement: PropTypes.element, - optionText: PropTypes.oneOfType([ - PropTypes.string, - PropTypes.func, - ]).isRequired, + optionText: PropTypes.oneOfType([PropTypes.string, PropTypes.func]) + .isRequired, optionValue: PropTypes.string.isRequired, resource: PropTypes.string, setFilter: PropTypes.func, diff --git a/src/mui/input/AutocompleteInput.spec.js b/src/mui/input/AutocompleteInput.spec.js index 0f25ae63..de07e3a8 100644 --- a/src/mui/input/AutocompleteInput.spec.js +++ b/src/mui/input/AutocompleteInput.spec.js @@ -12,31 +12,41 @@ describe('<AutocompleteInput />', () => { }; it('should use a mui AutoComplete', () => { - const wrapper = shallow(<AutocompleteInput {...defaultProps} input={{ value: 1 }} choices={[{ id: 1, name: 'hello' }]} />); + const wrapper = shallow( + <AutocompleteInput + {...defaultProps} + input={{ value: 1 }} + choices={[{ id: 1, name: 'hello' }]} + /> + ); const AutoCompleteElement = wrapper.find('AutoComplete'); assert.equal(AutoCompleteElement.length, 1); assert.equal(AutoCompleteElement.prop('searchText'), 'hello'); }); it('should use the input parameter value as the initial input searchText', () => { - const wrapper = shallow(<AutocompleteInput - {...defaultProps} - input={{ value: 2 }} - choices={[{ id: 2, name: 'foo' }]} - />); + const wrapper = shallow( + <AutocompleteInput + {...defaultProps} + input={{ value: 2 }} + choices={[{ id: 2, name: 'foo' }]} + /> + ); const AutoCompleteElement = wrapper.find('AutoComplete').first(); assert.equal(AutoCompleteElement.prop('searchText'), 'foo'); }); it('should pass choices as dataSource', () => { - const wrapper = shallow(<AutocompleteInput - {...defaultProps} - choices={[ - { id: 'M', name: 'Male' }, - { id: 'F', name: 'Female' }, - ]} - options={{ open: true }} - />); + const wrapper = shallow( + <AutocompleteInput + {...defaultProps} + choices={[ + { id: 'M', name: 'Male' }, + { id: 'F', name: 'Female' }, + ]} + options={{ open: true }} + /> + ); const AutoCompleteElement = wrapper.find('AutoComplete').first(); assert.deepEqual(AutoCompleteElement.prop('dataSource'), [ { value: 'M', text: 'Male' }, @@ -45,13 +55,13 @@ describe('<AutocompleteInput />', () => { }); it('should use optionValue as value identifier', () => { - const wrapper = shallow(<AutocompleteInput - {...defaultProps} - optionValue="foobar" - choices={[ - { foobar: 'M', name: 'Male' }, - ]} - />); + const wrapper = shallow( + <AutocompleteInput + {...defaultProps} + optionValue="foobar" + choices={[{ foobar: 'M', name: 'Male' }]} + /> + ); const AutoCompleteElement = wrapper.find('AutoComplete').first(); assert.deepEqual(AutoCompleteElement.prop('dataSource'), [ { value: 'M', text: 'Male' }, @@ -59,13 +69,13 @@ describe('<AutocompleteInput />', () => { }); it('should use optionValue including "." as value identifier', () => { - const wrapper = shallow(<AutocompleteInput - {...defaultProps} - optionValue="foobar.id" - choices={[ - { foobar: { id: 'M' }, name: 'Male' }, - ]} - />); + const wrapper = shallow( + <AutocompleteInput + {...defaultProps} + optionValue="foobar.id" + choices={[{ foobar: { id: 'M' }, name: 'Male' }]} + /> + ); const AutoCompleteElement = wrapper.find('AutoComplete').first(); assert.deepEqual(AutoCompleteElement.prop('dataSource'), [ { value: 'M', text: 'Male' }, @@ -73,13 +83,13 @@ describe('<AutocompleteInput />', () => { }); it('should use optionText with a string value as text identifier', () => { - const wrapper = shallow(<AutocompleteInput - {...defaultProps} - optionText="foobar" - choices={[ - { id: 'M', foobar: 'Male' }, - ]} - />); + const wrapper = shallow( + <AutocompleteInput + {...defaultProps} + optionText="foobar" + choices={[{ id: 'M', foobar: 'Male' }]} + /> + ); const AutoCompleteElement = wrapper.find('AutoComplete').first(); assert.deepEqual(AutoCompleteElement.prop('dataSource'), [ { value: 'M', text: 'Male' }, @@ -87,13 +97,13 @@ describe('<AutocompleteInput />', () => { }); it('should use optionText with a string value including "." as text identifier', () => { - const wrapper = shallow(<AutocompleteInput - {...defaultProps} - optionText="foobar.name" - choices={[ - { id: 'M', foobar: { name: 'Male' } }, - ]} - />); + const wrapper = shallow( + <AutocompleteInput + {...defaultProps} + optionText="foobar.name" + choices={[{ id: 'M', foobar: { name: 'Male' } }]} + /> + ); const AutoCompleteElement = wrapper.find('AutoComplete').first(); assert.deepEqual(AutoCompleteElement.prop('dataSource'), [ { value: 'M', text: 'Male' }, @@ -101,13 +111,13 @@ describe('<AutocompleteInput />', () => { }); it('should use optionText with a function value as text identifier', () => { - const wrapper = shallow(<AutocompleteInput - {...defaultProps} - optionText={choice => choice.foobar} - choices={[ - { id: 'M', foobar: 'Male' }, - ]} - />); + const wrapper = shallow( + <AutocompleteInput + {...defaultProps} + optionText={choice => choice.foobar} + choices={[{ id: 'M', foobar: 'Male' }]} + /> + ); const AutoCompleteElement = wrapper.find('AutoComplete').first(); assert.deepEqual(AutoCompleteElement.prop('dataSource'), [ { value: 'M', text: 'Male' }, @@ -115,14 +125,16 @@ describe('<AutocompleteInput />', () => { }); it('should translate the choices by default', () => { - const wrapper = shallow(<AutocompleteInput - {...defaultProps} - choices={[ - { id: 'M', name: 'Male' }, - { id: 'F', name: 'Female' }, - ]} - translate={x => `**${x}**`} - />); + const wrapper = shallow( + <AutocompleteInput + {...defaultProps} + choices={[ + { id: 'M', name: 'Male' }, + { id: 'F', name: 'Female' }, + ]} + translate={x => `**${x}**`} + /> + ); const AutoCompleteElement = wrapper.find('AutoComplete').first(); assert.deepEqual(AutoCompleteElement.prop('dataSource'), [ { value: 'M', text: '**Male**' }, @@ -131,15 +143,17 @@ describe('<AutocompleteInput />', () => { }); it('should not translate the choices if translateChoice is false', () => { - const wrapper = shallow(<AutocompleteInput - {...defaultProps} - choices={[ - { id: 'M', name: 'Male' }, - { id: 'F', name: 'Female' }, - ]} - translate={x => `**${x}**`} - translateChoice={false} - />); + const wrapper = shallow( + <AutocompleteInput + {...defaultProps} + choices={[ + { id: 'M', name: 'Male' }, + { id: 'F', name: 'Female' }, + ]} + translate={x => `**${x}**`} + translateChoice={false} + /> + ); const AutoCompleteElement = wrapper.find('AutoComplete').first(); assert.deepEqual(AutoCompleteElement.prop('dataSource'), [ { value: 'M', text: 'Male' }, @@ -149,21 +163,39 @@ describe('<AutocompleteInput />', () => { describe('error message', () => { it('should not be displayed if field is pristine', () => { - const wrapper = shallow(<AutocompleteInput {...defaultProps} meta={{ touched: false }} />); + const wrapper = shallow( + <AutocompleteInput + {...defaultProps} + meta={{ touched: false }} + /> + ); const AutoCompleteElement = wrapper.find('AutoComplete'); assert.equal(AutoCompleteElement.prop('errorText'), false); }); it('should not be displayed if field has been touched but is valid', () => { - const wrapper = shallow(<AutocompleteInput {...defaultProps} meta={{ touched: true, error: false }} />); + const wrapper = shallow( + <AutocompleteInput + {...defaultProps} + meta={{ touched: true, error: false }} + /> + ); const AutoCompleteElement = wrapper.find('AutoComplete'); assert.equal(AutoCompleteElement.prop('errorText'), false); }); it('should be displayed if field has been touched and is invalid', () => { - const wrapper = shallow(<AutocompleteInput {...defaultProps} meta={{ touched: true, error: 'Required field.' }} />); + const wrapper = shallow( + <AutocompleteInput + {...defaultProps} + meta={{ touched: true, error: 'Required field.' }} + /> + ); const AutoCompleteElement = wrapper.find('AutoComplete'); - assert.equal(AutoCompleteElement.prop('errorText'), 'Required field.'); + assert.equal( + AutoCompleteElement.prop('errorText'), + 'Required field.' + ); }); }); }); diff --git a/src/mui/input/BooleanInput.js b/src/mui/input/BooleanInput.js index c1effe02..ce265ca3 100644 --- a/src/mui/input/BooleanInput.js +++ b/src/mui/input/BooleanInput.js @@ -16,18 +16,32 @@ const styles = { }, }; -const BooleanInput = ({ input, isRequired, label, source, elStyle, resource, options }) => ( +const BooleanInput = ({ + input, + isRequired, + label, + source, + elStyle, + resource, + options, +}) => <div style={elStyle || styles.block}> <Toggle defaultToggled={!!input.value} onToggle={input.onChange} labelStyle={styles.label} style={styles.toggle} - label={<FieldTitle label={label} source={source} resource={resource} isRequired={isRequired} />} + label={ + <FieldTitle + label={label} + source={source} + resource={resource} + isRequired={isRequired} + /> + } {...options} /> - </div> -); + </div>; BooleanInput.propTypes = { addField: PropTypes.bool.isRequired, diff --git a/src/mui/input/BooleanInput.spec.js b/src/mui/input/BooleanInput.spec.js index 8c623408..77bda9be 100644 --- a/src/mui/input/BooleanInput.spec.js +++ b/src/mui/input/BooleanInput.spec.js @@ -5,20 +5,23 @@ import React from 'react'; import BooleanInput from './BooleanInput'; describe('<BooleanInput />', () => { - it('should render as a mui Toggle', () => { - const wrapper = shallow(<BooleanInput source="foo" input={{}}/>); + const wrapper = shallow(<BooleanInput source="foo" input={{}} />); const choices = wrapper.find('Toggle'); assert.equal(choices.length, 1); }); it('should be checked if the value is true', () => { - const wrapper = shallow(<BooleanInput source="foo" input={{ value: true }} />); + const wrapper = shallow( + <BooleanInput source="foo" input={{ value: true }} /> + ); assert.equal(wrapper.find('Toggle').prop('defaultToggled'), true); }); it('should not be checked if the value is false', () => { - const wrapper = shallow(<BooleanInput source="foo" input={{ value: false }} />); + const wrapper = shallow( + <BooleanInput source="foo" input={{ value: false }} /> + ); assert.equal(wrapper.find('Toggle').prop('defaultToggled'), false); }); @@ -26,5 +29,4 @@ describe('<BooleanInput />', () => { const wrapper = shallow(<BooleanInput source="foo" input={{}} />); assert.equal(wrapper.find('Toggle').prop('defaultToggled'), false); }); - }); diff --git a/src/mui/input/CheckboxGroupInput.js b/src/mui/input/CheckboxGroupInput.js index 5552860f..20d2c67e 100644 --- a/src/mui/input/CheckboxGroupInput.js +++ b/src/mui/input/CheckboxGroupInput.js @@ -8,13 +8,10 @@ import compose from 'recompose/compose'; import FieldTitle from '../../util/FieldTitle'; import translate from '../../i18n/translate'; -const getStyles = (muiTheme) => { +const getStyles = muiTheme => { const { baseTheme, - textField: { - floatingLabelColor, - backgroundColor, - }, + textField: { floatingLabelColor, backgroundColor }, } = muiTheme; return { @@ -109,11 +106,11 @@ export class CheckboxGroupInputComponent extends Component { if (isChecked) { onChange([...value, ...[event.target.value]]); } else { - onChange(value.filter(v => (v != event.target.value))); + onChange(value.filter(v => v != event.target.value)); } }; - renderCheckbox = (choice) => { + renderCheckbox = choice => { const { input: { value }, optionText, @@ -122,26 +119,41 @@ export class CheckboxGroupInputComponent extends Component { translate, translateChoice, } = this.props; - const choiceName = React.isValidElement(optionText) ? // eslint-disable-line no-nested-ternary - React.cloneElement(optionText, { record: choice }) : - (typeof optionText === 'function' ? - optionText(choice) : - get(choice, optionText) - ); + const choiceName = React.isValidElement(optionText) // eslint-disable-line no-nested-ternary + ? React.cloneElement(optionText, { record: choice }) + : typeof optionText === 'function' + ? optionText(choice) + : get(choice, optionText); return ( <Checkbox key={get(choice, optionValue)} - checked={value ? value.find(v => (v == get(choice, optionValue))) !== undefined : false} + checked={ + value + ? value.find(v => v == get(choice, optionValue)) !== + undefined + : false + } onCheck={this.handleCheck} value={get(choice, optionValue)} - label={translateChoice ? translate(choiceName, { _: choiceName }) : choiceName} + label={ + translateChoice + ? translate(choiceName, { _: choiceName }) + : choiceName + } {...options} /> ); - } + }; render() { - const { choices, isRequired, label, muiTheme, resource, source } = this.props; + const { + choices, + isRequired, + label, + muiTheme, + resource, + source, + } = this.props; const styles = getStyles(muiTheme); const { prepareStyles } = muiTheme; @@ -149,7 +161,12 @@ export class CheckboxGroupInputComponent extends Component { <div> <div style={prepareStyles(styles.labelContainer)}> <div style={prepareStyles(styles.label)}> - <FieldTitle label={label} source={source} resource={resource} isRequired={isRequired} /> + <FieldTitle + label={label} + source={source} + resource={resource} + isRequired={isRequired} + /> </div> </div> {choices.map(this.renderCheckbox)} diff --git a/src/mui/input/CheckboxGroupInput.spec.js b/src/mui/input/CheckboxGroupInput.spec.js index e70979ea..06478a67 100644 --- a/src/mui/input/CheckboxGroupInput.spec.js +++ b/src/mui/input/CheckboxGroupInput.spec.js @@ -7,9 +7,7 @@ describe('<CheckboxGroupInput />', () => { const defaultProps = { source: 'foo', meta: {}, - choices: [ - { id: 1, name: 'John doe' }, - ], + choices: [{ id: 1, name: 'John doe' }], input: { onChange: () => {}, value: [], @@ -22,7 +20,6 @@ describe('<CheckboxGroupInput />', () => { }, }; - it('should use a mui Checkbox', () => { const wrapper = shallow(<CheckboxGroupInput {...defaultProps} />); const CheckboxElement = wrapper.find('Checkbox'); @@ -30,19 +27,26 @@ describe('<CheckboxGroupInput />', () => { }); it('should use the input parameter value as the initial input value', () => { - const wrapper = shallow(<CheckboxGroupInput {...defaultProps} input={{ value: [1], onChange: () => {} }} />); + const wrapper = shallow( + <CheckboxGroupInput + {...defaultProps} + input={{ value: [1], onChange: () => {} }} + /> + ); const CheckboxElement = wrapper.find('Checkbox').first(); assert.equal(CheckboxElement.prop('checked'), true); }); it('should render choices as mui Checkbox components', () => { - const wrapper = shallow(<CheckboxGroupInput - {...defaultProps} - choices={[ - { id: 'ang', name: 'Angular' }, - { id: 'rct', name: 'React' }, - ]} - />); + const wrapper = shallow( + <CheckboxGroupInput + {...defaultProps} + choices={[ + { id: 'ang', name: 'Angular' }, + { id: 'rct', name: 'React' }, + ]} + /> + ); const CheckboxElements = wrapper.find('Checkbox'); assert.equal(CheckboxElements.length, 2); const CheckboxElement1 = CheckboxElements.first(); @@ -54,13 +58,13 @@ describe('<CheckboxGroupInput />', () => { }); it('should use optionValue as value identifier', () => { - const wrapper = shallow(<CheckboxGroupInput - {...defaultProps} - optionValue="foobar" - choices={[ - { foobar: 'foo', name: 'Bar' }, - ]} - />); + const wrapper = shallow( + <CheckboxGroupInput + {...defaultProps} + optionValue="foobar" + choices={[{ foobar: 'foo', name: 'Bar' }]} + /> + ); const CheckboxElements = wrapper.find('Checkbox'); const CheckboxElement1 = CheckboxElements.first(); assert.equal(CheckboxElement1.prop('value'), 'foo'); @@ -68,13 +72,13 @@ describe('<CheckboxGroupInput />', () => { }); it('should use optionValue including "." as value identifier', () => { - const wrapper = shallow(<CheckboxGroupInput - {...defaultProps} - optionValue="foobar.id" - choices={[ - { foobar: { id: 'foo' }, name: 'Bar' }, - ]} - />); + const wrapper = shallow( + <CheckboxGroupInput + {...defaultProps} + optionValue="foobar.id" + choices={[{ foobar: { id: 'foo' }, name: 'Bar' }]} + /> + ); const CheckboxElements = wrapper.find('Checkbox'); const CheckboxElement1 = CheckboxElements.first(); assert.equal(CheckboxElement1.prop('value'), 'foo'); @@ -82,13 +86,13 @@ describe('<CheckboxGroupInput />', () => { }); it('should use optionText with a string value as text identifier', () => { - const wrapper = shallow(<CheckboxGroupInput - {...defaultProps} - optionText="foobar" - choices={[ - { id: 'foo', foobar: 'Bar' }, - ]} - />); + const wrapper = shallow( + <CheckboxGroupInput + {...defaultProps} + optionText="foobar" + choices={[{ id: 'foo', foobar: 'Bar' }]} + /> + ); const CheckboxElements = wrapper.find('Checkbox'); const CheckboxElement1 = CheckboxElements.first(); assert.equal(CheckboxElement1.prop('value'), 'foo'); @@ -96,13 +100,13 @@ describe('<CheckboxGroupInput />', () => { }); it('should use optionText with a string value including "." as text identifier', () => { - const wrapper = shallow(<CheckboxGroupInput - {...defaultProps} - optionText="foobar.name" - choices={[ - { id: 'foo', foobar: { name: 'Bar' } }, - ]} - />); + const wrapper = shallow( + <CheckboxGroupInput + {...defaultProps} + optionText="foobar.name" + choices={[{ id: 'foo', foobar: { name: 'Bar' } }]} + /> + ); const CheckboxElements = wrapper.find('Checkbox'); const CheckboxElement1 = CheckboxElements.first(); assert.equal(CheckboxElement1.prop('value'), 'foo'); @@ -110,13 +114,13 @@ describe('<CheckboxGroupInput />', () => { }); it('should use optionText with a function value as text identifier', () => { - const wrapper = shallow(<CheckboxGroupInput - {...defaultProps} - optionText={choice => choice.foobar} - choices={[ - { id: 'foo', foobar: 'Bar' }, - ]} - />); + const wrapper = shallow( + <CheckboxGroupInput + {...defaultProps} + optionText={choice => choice.foobar} + choices={[{ id: 'foo', foobar: 'Bar' }]} + /> + ); const CheckboxElements = wrapper.find('Checkbox'); const CheckboxElement1 = CheckboxElements.first(); assert.equal(CheckboxElement1.prop('value'), 'foo'); @@ -124,44 +128,54 @@ describe('<CheckboxGroupInput />', () => { }); it('should use optionText with an element value as text identifier', () => { - const Foobar = ({ record }) => <span>{record.foobar}</span>; - const wrapper = shallow(<CheckboxGroupInput - {...defaultProps} - optionText={<Foobar />} - choices={[ - { id: 'foo', foobar: 'Bar' }, - ]} - />); + const Foobar = ({ record }) => + <span> + {record.foobar} + </span>; + const wrapper = shallow( + <CheckboxGroupInput + {...defaultProps} + optionText={<Foobar />} + choices={[{ id: 'foo', foobar: 'Bar' }]} + /> + ); const CheckboxElements = wrapper.find('Checkbox'); const CheckboxElement1 = CheckboxElements.first(); assert.equal(CheckboxElement1.prop('value'), 'foo'); - assert.deepEqual(CheckboxElement1.prop('label'), <Foobar record={{ id: 'foo', foobar: 'Bar' }} />); + assert.deepEqual( + CheckboxElement1.prop('label'), + <Foobar record={{ id: 'foo', foobar: 'Bar' }} /> + ); }); it('should translate the choices by default', () => { - const wrapper = shallow(<CheckboxGroupInput - {...defaultProps} - choices={[ - { id: 'M', name: 'Male' }, - { id: 'F', name: 'Female' }, - ]} - translate={x => `**${x}**`} - />); + const wrapper = shallow( + <CheckboxGroupInput + {...defaultProps} + choices={[ + { id: 'M', name: 'Male' }, + { id: 'F', name: 'Female' }, + ]} + translate={x => `**${x}**`} + /> + ); const CheckboxElements = wrapper.find('Checkbox'); const CheckboxElement1 = CheckboxElements.first(); assert.equal(CheckboxElement1.prop('label'), '**Male**'); }); it('should not translate the choices if translateChoice is false', () => { - const wrapper = shallow(<CheckboxGroupInput - {...defaultProps} - choices={[ - { id: 'M', name: 'Male' }, - { id: 'F', name: 'Female' }, - ]} - translate={x => `**${x}**`} - translateChoice={false} - />); + const wrapper = shallow( + <CheckboxGroupInput + {...defaultProps} + choices={[ + { id: 'M', name: 'Male' }, + { id: 'F', name: 'Female' }, + ]} + translate={x => `**${x}**`} + translateChoice={false} + /> + ); const CheckboxElements = wrapper.find('Checkbox'); const CheckboxElement1 = CheckboxElements.first(); assert.equal(CheckboxElement1.prop('label'), 'Male'); diff --git a/src/mui/input/DateInput.spec.js b/src/mui/input/DateInput.spec.js index b077c6f5..63c5701e 100644 --- a/src/mui/input/DateInput.spec.js +++ b/src/mui/input/DateInput.spec.js @@ -23,7 +23,10 @@ describe('DateInput .datify', () => { }); it('should return a date Object whichever non-null input is given', () => { - assert.deepEqual(datify(new Date('2010-05-01')), new Date('2010-05-01')); + assert.deepEqual( + datify(new Date('2010-05-01')), + new Date('2010-05-01') + ); assert.deepEqual(datify('2010-05-01'), new Date('2010-05-01')); }); }); @@ -31,7 +34,14 @@ describe('DateInput .datify', () => { describe('<DateInput />', () => { it('should render a localized <DatePicker />', () => { const input = { value: null }; - const wrapper = shallow(<DateInput source="foo" meta={{}} input={input} options={{ locale:'de-DE' }} />); + const wrapper = shallow( + <DateInput + source="foo" + meta={{}} + input={input} + options={{ locale: 'de-DE' }} + /> + ); const datePicker = wrapper.find('DatePicker'); assert.equal(datePicker.length, 1); @@ -50,19 +60,37 @@ describe('<DateInput />', () => { describe('error message', () => { it('should not be displayed if field is pristine', () => { - const wrapper = shallow(<DateInput source="foo" input={{ value: null }} meta={{ touched: false }} />); + const wrapper = shallow( + <DateInput + source="foo" + input={{ value: null }} + meta={{ touched: false }} + /> + ); const DatePicker = wrapper.find('DatePicker'); assert.equal(DatePicker.prop('errorText'), false); }); it('should not be displayed if field has been touched but is valid', () => { - const wrapper = shallow(<DateInput source="foo" input={{ value: null }} meta={{ touched: true, error: false }} />); + const wrapper = shallow( + <DateInput + source="foo" + input={{ value: null }} + meta={{ touched: true, error: false }} + /> + ); const DatePicker = wrapper.find('DatePicker'); assert.equal(DatePicker.prop('errorText'), false); }); it('should be displayed if field has been touched and is invalid', () => { - const wrapper = shallow(<DateInput source="foo" input={{ value: null }} meta={{ touched: true, error: 'Required field.' }} />); + const wrapper = shallow( + <DateInput + source="foo" + input={{ value: null }} + meta={{ touched: true, error: 'Required field.' }} + /> + ); const DatePicker = wrapper.find('DatePicker'); assert.equal(DatePicker.prop('errorText'), 'Required field.'); }); diff --git a/src/mui/input/DisabledInput.js b/src/mui/input/DisabledInput.js index 55086bab..8278a208 100644 --- a/src/mui/input/DisabledInput.js +++ b/src/mui/input/DisabledInput.js @@ -3,12 +3,21 @@ import PropTypes from 'prop-types'; import TextField from 'material-ui/TextField'; import FieldTitle from '../../util/FieldTitle'; -const DisabledInput = ({ input: { value }, label, resource, source, elStyle }) => <TextField - value={value} - floatingLabelText={<FieldTitle label={label} source={source} resource={resource} />} - style={elStyle} - disabled -/>; +const DisabledInput = ({ + input: { value }, + label, + resource, + source, + elStyle, +}) => + <TextField + value={value} + floatingLabelText={ + <FieldTitle label={label} source={source} resource={resource} /> + } + style={elStyle} + disabled + />; DisabledInput.propTypes = { label: PropTypes.string, @@ -22,6 +31,6 @@ DisabledInput.propTypes = { DisabledInput.defaultProps = { addField: true, -} +}; export default DisabledInput; diff --git a/src/mui/input/FileInput.spec.js b/src/mui/input/FileInput.spec.js index dbed31b5..5d705da3 100644 --- a/src/mui/input/FileInput.spec.js +++ b/src/mui/input/FileInput.spec.js @@ -14,7 +14,7 @@ describe('<FileInput />', () => { }); it('should display a dropzone', () => { - const wrapper = shallow(( + const wrapper = shallow( <FileInput input={{ value: { @@ -24,14 +24,14 @@ describe('<FileInput />', () => { translate={x => x} source="picture" /> - )); + ); assert.equal(wrapper.find('Dropzone').length, 1); }); it('should display correct label depending multiple property', () => { const test = (multiple, expectedLabel) => { - const wrapper = shallow(( + const wrapper = shallow( <FileInput multiple={multiple} input={{ @@ -42,7 +42,7 @@ describe('<FileInput />', () => { translate={x => x} source="picture" /> - )); + ); assert.equal(wrapper.find('Dropzone p').text(), expectedLabel); }; @@ -52,8 +52,8 @@ describe('<FileInput />', () => { }); it('should display correct custom label', () => { - const test = (expectedLabel) => { - const wrapper = shallow(( + const test = expectedLabel => { + const wrapper = shallow( <FileInput placeholder={expectedLabel} input={{ @@ -64,13 +64,11 @@ describe('<FileInput />', () => { translate={x => x} source="picture" /> - )); + ); assert.ok(wrapper.find('Dropzone').contains(expectedLabel)); }; - const CustomLabel = () => ( - <div>Custom label</div> - ); + const CustomLabel = () => <div>Custom label</div>; test('custom label'); test(<h1>Custom label</h1>); @@ -170,7 +168,7 @@ describe('<FileInput />', () => { describe('Image Preview', () => { it('should display file preview using child as preview component', () => { - const wrapper = shallow(( + const wrapper = shallow( <FileInput input={{ value: { @@ -182,7 +180,7 @@ describe('<FileInput />', () => { > <ImageField source="url" title="title" /> </FileInput> - )); + ); const previewImage = wrapper.find('ImageField'); @@ -196,32 +194,50 @@ describe('<FileInput />', () => { }); it('should display all files (when several) previews using child as preview component', () => { - const wrapper = shallow(( + const wrapper = shallow( <FileInput input={{ value: [ - { url: 'http://foo.com/bar.jpg', title: 'Hello world!' }, - { url: 'http://foo.com/qux.bmp', title: 'A good old Bitmap!' }, + { + url: 'http://foo.com/bar.jpg', + title: 'Hello world!', + }, + { + url: 'http://foo.com/qux.bmp', + title: 'A good old Bitmap!', + }, ], }} translate={x => x} > <ImageField source="url" title="title" /> </FileInput> - )); + ); const previewImages = wrapper.find('ImageField'); assert.equal(previewImages.length, 2); assert.equal(previewImages.at(0).prop('source'), 'url'); assert.equal(previewImages.at(0).prop('title'), 'title'); - assert.deepEqual(previewImages.at(0).prop('record').title, 'Hello world!'); - assert.deepEqual(previewImages.at(0).prop('record').url, 'http://foo.com/bar.jpg'); + assert.deepEqual( + previewImages.at(0).prop('record').title, + 'Hello world!' + ); + assert.deepEqual( + previewImages.at(0).prop('record').url, + 'http://foo.com/bar.jpg' + ); assert.equal(previewImages.at(1).prop('source'), 'url'); assert.equal(previewImages.at(1).prop('title'), 'title'); - assert.deepEqual(previewImages.at(1).prop('record').title, 'A good old Bitmap!'); - assert.deepEqual(previewImages.at(1).prop('record').url, 'http://foo.com/qux.bmp'); + assert.deepEqual( + previewImages.at(1).prop('record').title, + 'A good old Bitmap!' + ); + assert.deepEqual( + previewImages.at(1).prop('record').url, + 'http://foo.com/qux.bmp' + ); }); it('should update previews when updating input value', () => { @@ -236,7 +252,7 @@ describe('<FileInput />', () => { }} > <ImageField source="url" /> - </FileInput>, + </FileInput> ); const previewImage = wrapper.find('ImageField'); @@ -260,13 +276,9 @@ describe('<FileInput />', () => { it('should update previews when dropping a file', () => { const wrapper = shallow( - <FileInput - source="picture" - translate={x => x} - input={{}} - > + <FileInput source="picture" translate={x => x} input={{}}> <ImageField source="url" /> - </FileInput>, + </FileInput> ); wrapper.setProps({ @@ -300,14 +312,16 @@ describe('<FileInput />', () => { }} > <ImageField source="url" /> - </FileInput>, + </FileInput> ); const inputPreview = wrapper.find('FileInputPreview'); inputPreview.at(1).prop('onRemove')(); wrapper.update(); - const previewImages = wrapper.find('ImageField').map(f => f.prop('record')); + const previewImages = wrapper + .find('ImageField') + .map(f => f.prop('record')); assert.deepEqual(previewImages, [ { url: 'http://static.acme.com/foo.jpg' }, { url: 'http://static.acme.com/quz.jpg' }, diff --git a/src/mui/input/FileInputPreview.js b/src/mui/input/FileInputPreview.js index 3a0bc73a..5a3decff 100644 --- a/src/mui/input/FileInputPreview.js +++ b/src/mui/input/FileInputPreview.js @@ -32,10 +32,12 @@ export class FileInputPreview extends Component { render() { const { children, onRemove, itemStyle, removeStyle } = this.props; - const removeButtonStyle = this.state.hovered ? { - ...removeStyle, - ...styles.removeButtonHovered, - } : removeStyle; + const removeButtonStyle = this.state.hovered + ? { + ...removeStyle, + ...styles.removeButtonHovered, + } + : removeStyle; return ( <div @@ -43,10 +45,7 @@ export class FileInputPreview extends Component { onMouseOut={this.handleMouseOut} style={itemStyle} > - <IconButton - style={removeButtonStyle} - onClick={onRemove} - > + <IconButton style={removeButtonStyle} onClick={onRemove}> <RemoveCircle color={pinkA200} /> </IconButton> {children} diff --git a/src/mui/input/Labeled.js b/src/mui/input/Labeled.js index b773281e..38e35ccd 100644 --- a/src/mui/input/Labeled.js +++ b/src/mui/input/Labeled.js @@ -24,28 +24,46 @@ const defaultLabelStyle = { * </Labeled> */ const Labeled = () => { - const { input, isRequired, label, meta, resource, children, source, disabled = true, labelStyle = defaultLabelStyle, ...rest } = this.props; + const { + input, + isRequired, + label, + meta, + resource, + children, + source, + disabled = true, + labelStyle = defaultLabelStyle, + ...rest, + } = this.props; if (!label && !source) { throw new Error(`Cannot create label for component <${children && children.type && children.type.name}>: You must set either the label or source props. You can also disable automated label insertion by setting 'addLabel: false' in the component default props`); } + return ( <TextField - floatingLabelText={<FieldTitle label={label} source={source} resource={resource} isRequired={isRequired} />} + floatingLabelText={ + <FieldTitle + label={label} + source={source} + resource={resource} + isRequired={isRequired} + /> + } floatingLabelFixed fullWidth disabled={disabled} underlineShow={false} style={labelStyle} - errorText={meta && meta.touched && meta.error} > {children && typeof children.type !== 'string' ? - React.cloneElement(children, { input, meta, resource, ...rest }) : + React.cloneElement(children, { input, resource, ...rest }) : children } </TextField> ); -}; +} Labeled.propTypes = { basePath: PropTypes.string, diff --git a/src/mui/input/LongTextInput.js b/src/mui/input/LongTextInput.js index 930899a7..880479c2 100644 --- a/src/mui/input/LongTextInput.js +++ b/src/mui/input/LongTextInput.js @@ -3,9 +3,20 @@ import PropTypes from 'prop-types'; import TextField from 'material-ui/TextField'; import FieldTitle from '../../util/FieldTitle'; -const LongTextInput = ({ input, isRequired, label, meta, options, source, elStyle, resource }) => { +const LongTextInput = ({ + input, + isRequired, + label, + meta, + options, + source, + elStyle, + resource, +}) => { if (typeof meta === 'undefined') { - throw new Error('The LongTextInput component wasn\'t called within a redux-form <Field>. Did you decorate it and forget to add the addField prop to your component? See https://marmelab.com/admin-on-rest/Inputs.html#writing-your-own-input-component for details.'); + throw new Error( + "The LongTextInput component wasn't called within a redux-form <Field>. Did you decorate it and forget to add the addField prop to your component? See https://marmelab.com/admin-on-rest/Inputs.html#writing-your-own-input-component for details." + ); } const { touched, error } = meta; @@ -14,13 +25,20 @@ const LongTextInput = ({ input, isRequired, label, meta, options, source, elStyl {...input} multiLine fullWidth - floatingLabelText={<FieldTitle label={label} source={source} resource={resource} isRequired={isRequired} />} + floatingLabelText={ + <FieldTitle + label={label} + source={source} + resource={resource} + isRequired={isRequired} + /> + } errorText={touched && error} style={elStyle} {...options} /> ); -} +}; LongTextInput.propTypes = { addField: PropTypes.bool.isRequired, @@ -35,7 +53,7 @@ LongTextInput.propTypes = { source: PropTypes.string, validate: PropTypes.oneOfType([ PropTypes.func, - PropTypes.arrayOf(PropTypes.func) + PropTypes.arrayOf(PropTypes.func), ]), }; diff --git a/src/mui/input/LongTextInput.spec.js b/src/mui/input/LongTextInput.spec.js index ad71c0ac..a75747f7 100644 --- a/src/mui/input/LongTextInput.spec.js +++ b/src/mui/input/LongTextInput.spec.js @@ -6,19 +6,31 @@ import LongTextInput from './LongTextInput'; describe('<LongTextInput />', () => { describe('error message', () => { it('should not be displayed if field is pristine', () => { - const wrapper = shallow(<LongTextInput source="foo" meta={{ touched: false }} />); + const wrapper = shallow( + <LongTextInput source="foo" meta={{ touched: false }} /> + ); const TextFieldElement = wrapper.find('TextField'); assert.equal(TextFieldElement.prop('errorText'), false); }); it('should not be displayed if field has been touched but is valid', () => { - const wrapper = shallow(<LongTextInput source="foo" meta={{ touched: true, error: false }} />); + const wrapper = shallow( + <LongTextInput + source="foo" + meta={{ touched: true, error: false }} + /> + ); const TextFieldElement = wrapper.find('TextField'); assert.equal(TextFieldElement.prop('errorText'), false); }); it('should be displayed if field has been touched and is invalid', () => { - const wrapper = shallow(<LongTextInput source="foo" meta={{ touched: true, error: 'Required field.' }} />); + const wrapper = shallow( + <LongTextInput + source="foo" + meta={{ touched: true, error: 'Required field.' }} + /> + ); const TextFieldElement = wrapper.find('TextField'); assert.equal(TextFieldElement.prop('errorText'), 'Required field.'); }); diff --git a/src/mui/input/NullableBooleanInput.js b/src/mui/input/NullableBooleanInput.js index 1e87f237..7e44fb32 100644 --- a/src/mui/input/NullableBooleanInput.js +++ b/src/mui/input/NullableBooleanInput.js @@ -3,7 +3,15 @@ import PropTypes from 'prop-types'; import SelectInput from './SelectInput'; import translate from '../../i18n/translate'; -export const NullableBooleanInput = ({ input, meta, label, source, elStyle, resource, translate }) => ( +export const NullableBooleanInput = ({ + input, + meta, + label, + source, + elStyle, + resource, + translate, +}) => <SelectInput input={input} label={label} @@ -16,8 +24,7 @@ export const NullableBooleanInput = ({ input, meta, label, source, elStyle, reso ]} meta={meta} style={elStyle} - /> -); + />; NullableBooleanInput.propTypes = { addField: PropTypes.bool.isRequired, diff --git a/src/mui/input/NullableBooleanInput.spec.js b/src/mui/input/NullableBooleanInput.spec.js index d92263c1..4b759094 100644 --- a/src/mui/input/NullableBooleanInput.spec.js +++ b/src/mui/input/NullableBooleanInput.spec.js @@ -12,7 +12,9 @@ describe('<NullableBooleanInput />', () => { }; it('should give three different choices for true, false or unknown', () => { - const wrapper = shallow(<NullableBooleanInput source="foo" {...defaultProps} />); + const wrapper = shallow( + <NullableBooleanInput source="foo" {...defaultProps} /> + ); const choices = wrapper.find('getContext(SelectInput)').prop('choices'); assert.deepEqual(choices, [ { id: null, name: '' }, @@ -23,21 +25,42 @@ describe('<NullableBooleanInput />', () => { describe('error message', () => { it('should not be displayed if field is pristine', () => { - const wrapper = shallow(<NullableBooleanInput source="foo" {...defaultProps} meta={{ touched: false }} />); + const wrapper = shallow( + <NullableBooleanInput + source="foo" + {...defaultProps} + meta={{ touched: false }} + /> + ); const SelectInputElement = wrapper.find('getContext(SelectInput)'); assert.equal(SelectInputElement.prop('errorText'), undefined); }); it('should not be displayed if field has been touched but is valid', () => { - const wrapper = shallow(<NullableBooleanInput source="foo" {...defaultProps} meta={{ touched: true, error: false }} />); + const wrapper = shallow( + <NullableBooleanInput + source="foo" + {...defaultProps} + meta={{ touched: true, error: false }} + /> + ); const SelectInputElement = wrapper.find('getContext(SelectInput)'); assert.equal(SelectInputElement.prop('errorText'), undefined); }); it('should be displayed if field has been touched and is invalid', () => { - const wrapper = shallow(<NullableBooleanInput source="foo" {...defaultProps} meta={{ touched: true, error: 'Required field.' }} />); + const wrapper = shallow( + <NullableBooleanInput + source="foo" + {...defaultProps} + meta={{ touched: true, error: 'Required field.' }} + /> + ); const SelectInputElement = wrapper.find('getContext(SelectInput)'); - assert.deepEqual(SelectInputElement.prop('meta'), { touched: true, error: 'Required field.' }); + assert.deepEqual(SelectInputElement.prop('meta'), { + touched: true, + error: 'Required field.', + }); }); }); }); diff --git a/src/mui/input/NumberInput.js b/src/mui/input/NumberInput.js index a245b0fc..41baff10 100644 --- a/src/mui/input/NumberInput.js +++ b/src/mui/input/NumberInput.js @@ -16,7 +16,7 @@ import FieldTitle from '../../util/FieldTitle'; * The object passed as `options` props is passed to the material-ui <TextField> component */ class NumberInput extends Component { - handleBlur = (eventOrValue) => { + handleBlur = eventOrValue => { this.props.onBlur(eventOrValue); this.props.input.onBlur(eventOrValue); @@ -26,22 +26,34 @@ class NumberInput extends Component { */ const value = parseFloat(this.props.input.value); this.handleChange(isNaN(value) ? undefined : value); - } + }; - handleFocus = (event) => { + handleFocus = event => { this.props.onFocus(event); this.props.input.onFocus(event); - } + }; - handleChange = (eventOrValue) => { + handleChange = eventOrValue => { this.props.onChange(eventOrValue); this.props.input.onChange(eventOrValue); - } + }; render() { - const { elStyle, input, isRequired, label, meta, options, source, step, resource } = this.props; + const { + elStyle, + input, + isRequired, + label, + meta, + options, + source, + step, + resource, + } = this.props; if (typeof meta === 'undefined') { - throw new Error('The NumberInput component wasn\'t called within a redux-form <Field>. Did you decorate it and forget to add the addField prop to your component? See https://marmelab.com/admin-on-rest/Inputs.html#writing-your-own-input-component for details.'); + throw new Error( + "The NumberInput component wasn't called within a redux-form <Field>. Did you decorate it and forget to add the addField prop to your component? See https://marmelab.com/admin-on-rest/Inputs.html#writing-your-own-input-component for details." + ); } const { touched, error } = meta; @@ -53,7 +65,14 @@ class NumberInput extends Component { onChange={this.handleChange} type="number" step={step} - floatingLabelText={<FieldTitle label={label} source={source} resource={resource} isRequired={isRequired} />} + floatingLabelText={ + <FieldTitle + label={label} + source={source} + resource={resource} + isRequired={isRequired} + /> + } errorText={touched && error} style={elStyle} {...options} diff --git a/src/mui/input/NumberInput.spec.js b/src/mui/input/NumberInput.spec.js index 9486ebff..be4ec7bc 100644 --- a/src/mui/input/NumberInput.spec.js +++ b/src/mui/input/NumberInput.spec.js @@ -19,7 +19,9 @@ describe('<NumberInput />', () => { }; it('should use a mui TextField', () => { - const wrapper = shallow(<NumberInput {...defaultProps} input={{ value: 'hello' }} />); + const wrapper = shallow( + <NumberInput {...defaultProps} input={{ value: 'hello' }} /> + ); const TextFieldElement = wrapper.find('TextField'); assert.equal(TextFieldElement.length, 1); assert.equal(TextFieldElement.prop('value'), 'hello'); @@ -31,7 +33,9 @@ describe('<NumberInput />', () => { const onChange = sinon.spy(); const props = { ...defaultProps }; - const wrapper = shallow(<NumberInput {...props} onChange={onChange} />); + const wrapper = shallow( + <NumberInput {...props} onChange={onChange} /> + ); wrapper.find('TextField').simulate('change', 3); assert.deepEqual(onChange.args, [[3]]); @@ -40,7 +44,9 @@ describe('<NumberInput />', () => { it('should keep calling redux-form original event', () => { const onChange = sinon.spy(); - const wrapper = shallow(<NumberInput {...defaultProps} input={{ value: 2, onChange }} />); + const wrapper = shallow( + <NumberInput {...defaultProps} input={{ value: 2, onChange }} /> + ); wrapper.find('TextField').simulate('change', 3); assert.deepEqual(onChange.args, [[3]]); }); @@ -51,7 +57,9 @@ describe('<NumberInput />', () => { const onFocus = sinon.spy(); const props = { ...defaultProps }; - const wrapper = shallow(<NumberInput {...props} onFocus={onFocus} />); + const wrapper = shallow( + <NumberInput {...props} onFocus={onFocus} /> + ); wrapper.find('TextField').simulate('focus', 3); assert.deepEqual(onFocus.args, [[3]]); @@ -60,7 +68,9 @@ describe('<NumberInput />', () => { it('should keep calling redux-form original event', () => { const onFocus = sinon.spy(); - const wrapper = shallow(<NumberInput {...defaultProps} input={{ value: 2, onFocus }} />); + const wrapper = shallow( + <NumberInput {...defaultProps} input={{ value: 2, onFocus }} /> + ); wrapper.find('TextField').simulate('focus', 3); assert.deepEqual(onFocus.args, [[3]]); }); @@ -103,7 +113,7 @@ describe('<NumberInput />', () => { onBlur: () => {}, onChange, }} - />, + /> ); const TextFieldElement = wrapper.find('TextField').first(); diff --git a/src/mui/input/RadioButtonGroupInput.js b/src/mui/input/RadioButtonGroupInput.js index e2c8482d..cede13c7 100644 --- a/src/mui/input/RadioButtonGroupInput.js +++ b/src/mui/input/RadioButtonGroupInput.js @@ -66,34 +66,55 @@ import translate from '../../i18n/translate'; export class RadioButtonGroupInput extends Component { handleChange = (event, value) => { this.props.input.onChange(value); - } + }; - renderRadioButton = (choice) => { + renderRadioButton = choice => { const { optionText, optionValue, translate, translateChoice, } = this.props; - const choiceName = React.isValidElement(optionText) ? // eslint-disable-line no-nested-ternary - React.cloneElement(optionText, { record: choice }) : - (typeof optionText === 'function' ? - optionText(choice) : - get(choice, optionText) - ); + const choiceName = React.isValidElement(optionText) // eslint-disable-line no-nested-ternary + ? React.cloneElement(optionText, { record: choice }) + : typeof optionText === 'function' + ? optionText(choice) + : get(choice, optionText); return ( <RadioButton key={get(choice, optionValue)} - label={translateChoice ? translate(choiceName, { _: choiceName }) : choiceName} + label={ + translateChoice ? ( + translate(choiceName, { _: choiceName }) + ) : ( + choiceName + ) + } value={get(choice, optionValue)} /> ); - } + }; render() { - const { label, resource, source, input, isRequired, choices, options, elStyle } = this.props; + const { + label, + resource, + source, + input, + isRequired, + choices, + options, + elStyle, + } = this.props; + return ( - <Labeled label={label} onChange={this.handleChange} resource={resource} source={source} isRequired={isRequired}> + <Labeled + label={label} + onChange={this.handleChange} + resource={resource} + source={source} + isRequired={isRequired} + > <RadioButtonGroup name={source} defaultSelected={input.value} diff --git a/src/mui/input/RadioButtonGroupInput.spec.js b/src/mui/input/RadioButtonGroupInput.spec.js index 5eab6f1d..af799c99 100644 --- a/src/mui/input/RadioButtonGroupInput.spec.js +++ b/src/mui/input/RadioButtonGroupInput.spec.js @@ -12,25 +12,33 @@ describe('<RadioButtonGroupInput />', () => { }; it('should use a mui RadioButtonGroup', () => { - const wrapper = shallow(<RadioButtonGroupInput {...defaultProps} label="hello" />); + const wrapper = shallow( + <RadioButtonGroupInput {...defaultProps} label="hello" /> + ); const RadioButtonGroupElement = wrapper.find('RadioButtonGroup'); assert.equal(RadioButtonGroupElement.length, 1); }); it('should use the input parameter value as the initial input value', () => { - const wrapper = shallow(<RadioButtonGroupInput {...defaultProps} input={{ value: 2 }} />); - const RadioButtonGroupElement = wrapper.find('RadioButtonGroup').first(); + const wrapper = shallow( + <RadioButtonGroupInput {...defaultProps} input={{ value: 2 }} /> + ); + const RadioButtonGroupElement = wrapper + .find('RadioButtonGroup') + .first(); assert.equal(RadioButtonGroupElement.prop('defaultSelected'), '2'); }); it('should render choices as mui RadioButton components', () => { - const wrapper = shallow(<RadioButtonGroupInput - {...defaultProps} - choices={[ - { id: 'M', name: 'Male' }, - { id: 'F', name: 'Female' }, - ]} - />); + const wrapper = shallow( + <RadioButtonGroupInput + {...defaultProps} + choices={[ + { id: 'M', name: 'Male' }, + { id: 'F', name: 'Female' }, + ]} + /> + ); const RadioButtonElements = wrapper.find('RadioButton'); assert.equal(RadioButtonElements.length, 2); const RadioButtonElement1 = RadioButtonElements.first(); @@ -42,13 +50,13 @@ describe('<RadioButtonGroupInput />', () => { }); it('should use optionValue as value identifier', () => { - const wrapper = shallow(<RadioButtonGroupInput - {...defaultProps} - optionValue="foobar" - choices={[ - { foobar: 'M', name: 'Male' }, - ]} - />); + const wrapper = shallow( + <RadioButtonGroupInput + {...defaultProps} + optionValue="foobar" + choices={[{ foobar: 'M', name: 'Male' }]} + /> + ); const RadioButtonElements = wrapper.find('RadioButton'); const RadioButtonElement1 = RadioButtonElements.first(); assert.equal(RadioButtonElement1.prop('value'), 'M'); @@ -56,13 +64,13 @@ describe('<RadioButtonGroupInput />', () => { }); it('should use optionValue including "." as value identifier', () => { - const wrapper = shallow(<RadioButtonGroupInput - {...defaultProps} - optionValue="foobar.id" - choices={[ - { foobar: { id: 'M' }, name: 'Male' }, - ]} - />); + const wrapper = shallow( + <RadioButtonGroupInput + {...defaultProps} + optionValue="foobar.id" + choices={[{ foobar: { id: 'M' }, name: 'Male' }]} + /> + ); const RadioButtonElements = wrapper.find('RadioButton'); const RadioButtonElement1 = RadioButtonElements.first(); assert.equal(RadioButtonElement1.prop('value'), 'M'); @@ -70,13 +78,13 @@ describe('<RadioButtonGroupInput />', () => { }); it('should use optionText with a string value as text identifier', () => { - const wrapper = shallow(<RadioButtonGroupInput - {...defaultProps} - optionText="foobar" - choices={[ - { id: 'M', foobar: 'Male' }, - ]} - />); + const wrapper = shallow( + <RadioButtonGroupInput + {...defaultProps} + optionText="foobar" + choices={[{ id: 'M', foobar: 'Male' }]} + /> + ); const RadioButtonElements = wrapper.find('RadioButton'); const RadioButtonElement1 = RadioButtonElements.first(); assert.equal(RadioButtonElement1.prop('value'), 'M'); @@ -84,13 +92,13 @@ describe('<RadioButtonGroupInput />', () => { }); it('should use optionText with a string value including "." as text identifier', () => { - const wrapper = shallow(<RadioButtonGroupInput - {...defaultProps} - optionText="foobar.name" - choices={[ - { id: 'M', foobar: { name: 'Male' } }, - ]} - />); + const wrapper = shallow( + <RadioButtonGroupInput + {...defaultProps} + optionText="foobar.name" + choices={[{ id: 'M', foobar: { name: 'Male' } }]} + /> + ); const RadioButtonElements = wrapper.find('RadioButton'); const RadioButtonElement1 = RadioButtonElements.first(); assert.equal(RadioButtonElement1.prop('value'), 'M'); @@ -98,13 +106,13 @@ describe('<RadioButtonGroupInput />', () => { }); it('should use optionText with a function value as text identifier', () => { - const wrapper = shallow(<RadioButtonGroupInput - {...defaultProps} - optionText={choice => choice.foobar} - choices={[ - { id: 'M', foobar: 'Male' }, - ]} - />); + const wrapper = shallow( + <RadioButtonGroupInput + {...defaultProps} + optionText={choice => choice.foobar} + choices={[{ id: 'M', foobar: 'Male' }]} + /> + ); const RadioButtonElements = wrapper.find('RadioButton'); const RadioButtonElement1 = RadioButtonElements.first(); assert.equal(RadioButtonElement1.prop('value'), 'M'); @@ -112,44 +120,54 @@ describe('<RadioButtonGroupInput />', () => { }); it('should use optionText with an element value as text identifier', () => { - const Foobar = ({ record }) => <span>{record.foobar}</span>; - const wrapper = shallow(<RadioButtonGroupInput - {...defaultProps} - optionText={<Foobar />} - choices={[ - { id: 'M', foobar: 'Male' }, - ]} - />); + const Foobar = ({ record }) => + <span> + {record.foobar} + </span>; + const wrapper = shallow( + <RadioButtonGroupInput + {...defaultProps} + optionText={<Foobar />} + choices={[{ id: 'M', foobar: 'Male' }]} + /> + ); const RadioButtonElements = wrapper.find('RadioButton'); const RadioButtonElement1 = RadioButtonElements.first(); assert.equal(RadioButtonElement1.prop('value'), 'M'); - assert.deepEqual(RadioButtonElement1.prop('label'), <Foobar record={{ id: 'M', foobar: 'Male' }} />); + assert.deepEqual( + RadioButtonElement1.prop('label'), + <Foobar record={{ id: 'M', foobar: 'Male' }} /> + ); }); it('should translate the choices by default', () => { - const wrapper = shallow(<RadioButtonGroupInput - {...defaultProps} - choices={[ - { id: 'M', name: 'Male' }, - { id: 'F', name: 'Female' }, - ]} - translate={x => `**${x}**`} - />); + const wrapper = shallow( + <RadioButtonGroupInput + {...defaultProps} + choices={[ + { id: 'M', name: 'Male' }, + { id: 'F', name: 'Female' }, + ]} + translate={x => `**${x}**`} + /> + ); const RadioButtonElements = wrapper.find('RadioButton'); const RadioButtonElement1 = RadioButtonElements.first(); assert.equal(RadioButtonElement1.prop('label'), '**Male**'); }); it('should not translate the choices if translateChoice is false', () => { - const wrapper = shallow(<RadioButtonGroupInput - {...defaultProps} - choices={[ - { id: 'M', name: 'Male' }, - { id: 'F', name: 'Female' }, - ]} - translate={x => `**${x}**`} - translateChoice={false} - />); + const wrapper = shallow( + <RadioButtonGroupInput + {...defaultProps} + choices={[ + { id: 'M', name: 'Male' }, + { id: 'F', name: 'Female' }, + ]} + translate={x => `**${x}**`} + translateChoice={false} + /> + ); const RadioButtonElements = wrapper.find('RadioButton'); const RadioButtonElement1 = RadioButtonElements.first(); assert.equal(RadioButtonElement1.prop('label'), 'Male'); diff --git a/src/mui/input/ReferenceArrayInput.js b/src/mui/input/ReferenceArrayInput.js index ad073975..be63918d 100644 --- a/src/mui/input/ReferenceArrayInput.js +++ b/src/mui/input/ReferenceArrayInput.js @@ -108,58 +108,91 @@ export class ReferenceArrayInput extends Component { } } - setFilter = (filter) => { + setFilter = filter => { if (filter !== this.params.filter) { this.params.filter = this.props.filterToQuery(filter); this.fetchReferenceAndOptions(); } - } + }; - setPagination = (pagination) => { + setPagination = pagination => { if (pagination !== this.param.pagination) { this.param.pagination = pagination; this.fetchReferenceAndOptions(); } - } + }; - setSort = (sort) => { + setSort = sort => { if (sort !== this.params.sort) { this.params.sort = sort; this.fetchReferenceAndOptions(); } - } + }; - fetchReferenceAndOptions({ input, reference, source, resource } = this.props) { + fetchReferenceAndOptions( + { input, reference, source, resource } = this.props + ) { const { pagination, sort, filter } = this.params; const ids = input.value; if (ids) { if (!Array.isArray(ids)) { - throw Error('The value of ReferenceArrayInput should be an array'); + throw Error( + 'The value of ReferenceArrayInput should be an array' + ); } this.props.crudGetMany(reference, ids); } - this.props.crudGetMatching(reference, referenceSource(resource, source), pagination, sort, filter); + this.props.crudGetMatching( + reference, + referenceSource(resource, source), + pagination, + sort, + filter + ); } render() { - const { input, resource, label, source, referenceRecords, allowEmpty, matchingReferences, basePath, onChange, children, meta } = this.props; + const { + input, + resource, + label, + source, + referenceRecords, + allowEmpty, + matchingReferences, + basePath, + onChange, + children, + meta, + } = this.props; if (React.Children.count(children) !== 1) { - throw new Error('<ReferenceArrayInput> only accepts a single child (like <Datagrid>)'); + throw new Error( + '<ReferenceArrayInput> only accepts a single child (like <Datagrid>)' + ); } if (!(referenceRecords && referenceRecords.length > 0) && !allowEmpty) { - return <Labeled - label={typeof label === 'undefined' ? `resources.${resource}.fields.${source}` : label} - source={source} - resource={resource} - />; + return ( + <Labeled + label={ + typeof label === 'undefined' + ? `resources.${resource}.fields.${source}` + : label + } + source={source} + resource={resource} + /> + ); } return React.cloneElement(children, { allowEmpty, input, - label: typeof label === 'undefined' ? `resources.${resource}.fields.${source}` : label, + label: + typeof label === 'undefined' + ? `resources.${resource}.fields.${source}` + : label, resource, meta, source, @@ -223,7 +256,7 @@ function mapStateToProps(state, props) { state, referenceSource(props.resource, props.source), props.reference, - referenceIds, + referenceIds ), }; } diff --git a/src/mui/input/ReferenceArrayInput.spec.js b/src/mui/input/ReferenceArrayInput.spec.js index 3c86086b..c5952877 100644 --- a/src/mui/input/ReferenceArrayInput.spec.js +++ b/src/mui/input/ReferenceArrayInput.spec.js @@ -16,32 +16,37 @@ describe('<ReferenceArrayInput />', () => { const MyComponent = () => <span id="mycomponent" />; it('should not render anything if there is no referenceRecord and allowEmpty is false', () => { - const wrapper = shallow(( + const wrapper = shallow( <ReferenceArrayInput {...defaultProps}> <MyComponent /> </ReferenceArrayInput> - )); + ); const MyComponentElement = wrapper.find('MyComponent'); assert.equal(MyComponentElement.length, 0); }); it('should not render enclosed component if allowEmpty is true', () => { - const wrapper = shallow(( + const wrapper = shallow( <ReferenceArrayInput {...defaultProps} allowEmpty> <MyComponent /> </ReferenceArrayInput> - )); + ); const MyComponentElement = wrapper.find('MyComponent'); assert.equal(MyComponentElement.length, 1); }); it('should call crudGetMatching on mount with default fetch values', () => { const crudGetMatching = sinon.spy(); - shallow(( - <ReferenceArrayInput {...defaultProps} allowEmpty crudGetMatching={crudGetMatching}> + shallow( + <ReferenceArrayInput + {...defaultProps} + allowEmpty + crudGetMatching={crudGetMatching} + > <MyComponent /> - </ReferenceArrayInput> - ), { lifecycleExperimental: true }); + </ReferenceArrayInput>, + { lifecycleExperimental: true } + ); assert.deepEqual(crudGetMatching.args[0], [ 'tags', 'posts@tag_ids', @@ -59,7 +64,7 @@ describe('<ReferenceArrayInput />', () => { it('should allow to customize crudGetMatching arguments with perPage, sort, and filter props', () => { const crudGetMatching = sinon.spy(); - shallow(( + shallow( <ReferenceArrayInput {...defaultProps} allowEmpty @@ -69,8 +74,9 @@ describe('<ReferenceArrayInput />', () => { filter={{ q: 'foo' }} > <MyComponent /> - </ReferenceArrayInput> - ), { lifecycleExperimental: true }); + </ReferenceArrayInput>, + { lifecycleExperimental: true } + ); assert.deepEqual(crudGetMatching.args[0], [ 'tags', 'posts@tag_ids', @@ -90,15 +96,16 @@ describe('<ReferenceArrayInput />', () => { it('should call crudGetMatching when setFilter is called', () => { const crudGetMatching = sinon.spy(); - const wrapper = shallow(( + const wrapper = shallow( <ReferenceArrayInput {...defaultProps} allowEmpty crudGetMatching={crudGetMatching} > <MyComponent /> - </ReferenceArrayInput> - ), { lifecycleExperimental: true }); + </ReferenceArrayInput>, + { lifecycleExperimental: true } + ); wrapper.instance().setFilter('bar'); assert.deepEqual(crudGetMatching.args[1], [ 'tags', @@ -119,7 +126,7 @@ describe('<ReferenceArrayInput />', () => { it('should use custom filterToQuery function prop', () => { const crudGetMatching = sinon.spy(); - const wrapper = shallow(( + const wrapper = shallow( <ReferenceArrayInput {...defaultProps} allowEmpty @@ -127,8 +134,9 @@ describe('<ReferenceArrayInput />', () => { filterToQuery={searchText => ({ foo: searchText })} > <MyComponent /> - </ReferenceArrayInput> - ), { lifecycleExperimental: true }); + </ReferenceArrayInput>, + { lifecycleExperimental: true } + ); wrapper.instance().setFilter('bar'); assert.deepEqual(crudGetMatching.args[1], [ 'tags', @@ -149,7 +157,7 @@ describe('<ReferenceArrayInput />', () => { it('should call crudGetMany on mount if value is set', () => { const crudGetMany = sinon.spy(); - shallow(( + shallow( <ReferenceArrayInput {...defaultProps} allowEmpty @@ -157,17 +165,15 @@ describe('<ReferenceArrayInput />', () => { input={{ value: [5, 6] }} > <MyComponent /> - </ReferenceArrayInput> - ), { lifecycleExperimental: true }); - assert.deepEqual(crudGetMany.args[0], [ - 'tags', - [5, 6], - ]); + </ReferenceArrayInput>, + { lifecycleExperimental: true } + ); + assert.deepEqual(crudGetMany.args[0], ['tags', [5, 6]]); }); it('should pass onChange down to child component', () => { const onChange = sinon.spy(); - const wrapper = shallow(( + const wrapper = shallow( <ReferenceArrayInput {...defaultProps} allowEmpty @@ -175,11 +181,9 @@ describe('<ReferenceArrayInput />', () => { > <MyComponent /> </ReferenceArrayInput> - )); + ); wrapper.find('MyComponent').simulate('change', 'foo'); - assert.deepEqual(onChange.args[0], [ - 'foo', - ]); + assert.deepEqual(onChange.args[0], ['foo']); }); it('should pass meta down to child component', () => { @@ -190,7 +194,7 @@ describe('<ReferenceArrayInput />', () => { meta={{ touched: false }} > <MyComponent /> - </ReferenceArrayInput>, + </ReferenceArrayInput> ); const myComponent = wrapper.find('MyComponent'); diff --git a/src/mui/input/ReferenceInput.js b/src/mui/input/ReferenceInput.js index 41b4692b..9a09528e 100644 --- a/src/mui/input/ReferenceInput.js +++ b/src/mui/input/ReferenceInput.js @@ -3,7 +3,10 @@ import PropTypes from 'prop-types'; import { connect } from 'react-redux'; import debounce from 'lodash.debounce'; import Labeled from './Labeled'; -import { crudGetOne as crudGetOneAction, crudGetMatching as crudGetMatchingAction } from '../../actions/dataActions'; +import { + crudGetOne as crudGetOneAction, + crudGetMatching as crudGetMatchingAction, +} from '../../actions/dataActions'; import { getPossibleReferences } from '../../reducer/references/possibleValues'; const referenceSource = (resource, source) => `${resource}@${source}`; @@ -107,28 +110,30 @@ export class ReferenceInput extends Component { } } - setFilter = (filter) => { + setFilter = filter => { if (filter !== this.params.filter) { this.params.filter = this.props.filterToQuery(filter); this.fetchReferenceAndOptions(); } - } + }; - setPagination = (pagination) => { + setPagination = pagination => { if (pagination !== this.param.pagination) { this.param.pagination = pagination; this.fetchReferenceAndOptions(); } - } + }; - setSort = (sort) => { + setSort = sort => { if (sort !== this.params.sort) { this.params.sort = sort; this.fetchReferenceAndOptions(); } - } + }; - fetchReferenceAndOptions({ input, reference, source, resource } = this.props) { + fetchReferenceAndOptions( + { input, reference, source, resource } = this.props + ) { const { filter: filterFromProps } = this.props; const { pagination, sort, filter } = this.params; @@ -137,23 +142,51 @@ export class ReferenceInput extends Component { this.props.crudGetOne(reference, id, null, false); } const finalFilter = { ...filterFromProps, ...filter }; - this.props.crudGetMatching(reference, referenceSource(resource, source), pagination, sort, finalFilter); + this.props.crudGetMatching( + reference, + referenceSource(resource, source), + pagination, + sort, + finalFilter + ); } render() { - const { input, resource, label, source, reference, referenceRecord, allowEmpty, matchingReferences, basePath, onChange, children, meta } = this.props; + const { + input, + resource, + label, + source, + reference, + referenceRecord, + allowEmpty, + matchingReferences, + basePath, + onChange, + children, + meta, + } = this.props; if (!referenceRecord && !allowEmpty) { - return (<Labeled - label={typeof label === 'undefined' ? `resources.${resource}.fields.${source}` : label} - source={source} - resource={resource} - />); + return ( + <Labeled + label={ + typeof label === 'undefined' + ? `resources.${resource}.fields.${source}` + : label + } + source={source} + resource={resource} + /> + ); } return React.cloneElement(children, { allowEmpty, input, - label: typeof label === 'undefined' ? `resources.${resource}.fields.${source}` : label, + label: + typeof label === 'undefined' + ? `resources.${resource}.fields.${source}` + : label, resource, meta, source, @@ -209,7 +242,12 @@ function mapStateToProps(state, props) { const referenceId = props.input.value; return { referenceRecord: state.admin[props.reference].data[referenceId], - matchingReferences: getPossibleReferences(state, referenceSource(props.resource, props.source), props.reference, [referenceId]), + matchingReferences: getPossibleReferences( + state, + referenceSource(props.resource, props.source), + props.reference, + [referenceId] + ), }; } diff --git a/src/mui/input/ReferenceInput.spec.js b/src/mui/input/ReferenceInput.spec.js index 3a938975..e98f7a82 100644 --- a/src/mui/input/ReferenceInput.spec.js +++ b/src/mui/input/ReferenceInput.spec.js @@ -16,32 +16,37 @@ describe('<ReferenceInput />', () => { const MyComponent = () => <span id="mycomponent" />; it('should not render anything if there is no referenceRecord and allowEmpty is false', () => { - const wrapper = shallow(( + const wrapper = shallow( <ReferenceInput {...defaultProps}> <MyComponent /> </ReferenceInput> - )); + ); const MyComponentElement = wrapper.find('MyComponent'); assert.equal(MyComponentElement.length, 0); }); it('should not render enclosed component if allowEmpty is true', () => { - const wrapper = shallow(( + const wrapper = shallow( <ReferenceInput {...defaultProps} allowEmpty> <MyComponent /> </ReferenceInput> - )); + ); const MyComponentElement = wrapper.find('MyComponent'); assert.equal(MyComponentElement.length, 1); }); it('should call crudGetMatching on mount with default fetch values', () => { const crudGetMatching = sinon.spy(); - shallow(( - <ReferenceInput {...defaultProps} allowEmpty crudGetMatching={crudGetMatching}> + shallow( + <ReferenceInput + {...defaultProps} + allowEmpty + crudGetMatching={crudGetMatching} + > <MyComponent /> - </ReferenceInput> - ), { lifecycleExperimental: true }); + </ReferenceInput>, + { lifecycleExperimental: true } + ); assert.deepEqual(crudGetMatching.args[0], [ 'posts', 'comments@post_id', @@ -59,7 +64,7 @@ describe('<ReferenceInput />', () => { it('should allow to customize crudGetMatching arguments with perPage, sort, and filter props', () => { const crudGetMatching = sinon.spy(); - shallow(( + shallow( <ReferenceInput {...defaultProps} allowEmpty @@ -69,8 +74,9 @@ describe('<ReferenceInput />', () => { filter={{ q: 'foo' }} > <MyComponent /> - </ReferenceInput> - ), { lifecycleExperimental: true }); + </ReferenceInput>, + { lifecycleExperimental: true } + ); assert.deepEqual(crudGetMatching.args[0], [ 'posts', 'comments@post_id', @@ -90,7 +96,7 @@ describe('<ReferenceInput />', () => { it('should allow to customize crudGetMatching arguments with perPage, sort, and filter props without loosing original default filter', () => { const crudGetMatching = sinon.spy(); - const wrapper = shallow(( + const wrapper = shallow( <ReferenceInput {...defaultProps} allowEmpty @@ -100,40 +106,44 @@ describe('<ReferenceInput />', () => { filter={{ foo: 'bar' }} > <MyComponent /> - </ReferenceInput> - ), { lifecycleExperimental: true }); + </ReferenceInput>, + { lifecycleExperimental: true } + ); wrapper.instance().setFilter('search_me'); - assert(crudGetMatching.calledWith( - 'posts', - 'comments@post_id', - { - page: 1, - perPage: 5, - }, - { - field: 'foo', - order: 'ASC', - }, - { - foo: 'bar', - q: 'search_me', - }, - )); + assert( + crudGetMatching.calledWith( + 'posts', + 'comments@post_id', + { + page: 1, + perPage: 5, + }, + { + field: 'foo', + order: 'ASC', + }, + { + foo: 'bar', + q: 'search_me', + } + ) + ); }); it('should call crudGetMatching when setFilter is called', () => { const crudGetMatching = sinon.spy(); - const wrapper = shallow(( + const wrapper = shallow( <ReferenceInput {...defaultProps} allowEmpty crudGetMatching={crudGetMatching} > <MyComponent /> - </ReferenceInput> - ), { lifecycleExperimental: true }); + </ReferenceInput>, + { lifecycleExperimental: true } + ); wrapper.instance().setFilter('bar'); assert.deepEqual(crudGetMatching.args[1], [ 'posts', @@ -154,7 +164,7 @@ describe('<ReferenceInput />', () => { it('should use custom filterToQuery function prop', () => { const crudGetMatching = sinon.spy(); - const wrapper = shallow(( + const wrapper = shallow( <ReferenceInput {...defaultProps} allowEmpty @@ -162,8 +172,9 @@ describe('<ReferenceInput />', () => { filterToQuery={searchText => ({ foo: searchText })} > <MyComponent /> - </ReferenceInput> - ), { lifecycleExperimental: true }); + </ReferenceInput>, + { lifecycleExperimental: true } + ); wrapper.instance().setFilter('bar'); assert.deepEqual(crudGetMatching.args[1], [ 'posts', @@ -184,7 +195,7 @@ describe('<ReferenceInput />', () => { it('should call crudGetOne on mount if value is set', () => { const crudGetOne = sinon.spy(); - shallow(( + shallow( <ReferenceInput {...defaultProps} allowEmpty @@ -192,31 +203,21 @@ describe('<ReferenceInput />', () => { input={{ value: 5 }} > <MyComponent /> - </ReferenceInput> - ), { lifecycleExperimental: true }); - assert.deepEqual(crudGetOne.args[0], [ - 'posts', - 5, - null, - false, - ]); + </ReferenceInput>, + { lifecycleExperimental: true } + ); + assert.deepEqual(crudGetOne.args[0], ['posts', 5, null, false]); }); it('should pass onChange down to child component', () => { const onChange = sinon.spy(); - const wrapper = shallow(( - <ReferenceInput - {...defaultProps} - allowEmpty - onChange={onChange} - > + const wrapper = shallow( + <ReferenceInput {...defaultProps} allowEmpty onChange={onChange}> <MyComponent /> </ReferenceInput> - )); + ); wrapper.find('MyComponent').simulate('change', 'foo'); - assert.deepEqual(onChange.args[0], [ - 'foo', - ]); + assert.deepEqual(onChange.args[0], ['foo']); }); it('should pass meta down to child component', () => { @@ -227,7 +228,7 @@ describe('<ReferenceInput />', () => { meta={{ touched: false }} > <MyComponent /> - </ReferenceInput>, + </ReferenceInput> ); const myComponent = wrapper.find('MyComponent'); diff --git a/src/mui/input/SelectArrayInput.js b/src/mui/input/SelectArrayInput.js index 070a5fc0..e2aeacfc 100644 --- a/src/mui/input/SelectArrayInput.js +++ b/src/mui/input/SelectArrayInput.js @@ -57,17 +57,23 @@ export class SelectArrayInput extends Component { componentWillMount = () => { this.setState({ - values: this.getChoicesForValues(this.props.input.value || [], this.props.choices), + values: this.getChoicesForValues( + this.props.input.value || [], + this.props.choices + ), }); - } + }; - componentWillReceiveProps = (nextProps) => { + componentWillReceiveProps = nextProps => { if ( this.props.choices !== nextProps.choices || this.props.input.value !== nextProps.input.value ) { this.setState({ - values: this.getChoicesForValues(nextProps.input.value || [], nextProps.choices), + values: this.getChoicesForValues( + nextProps.input.value || [], + nextProps.choices + ), }); } }; @@ -84,26 +90,29 @@ export class SelectArrayInput extends Component { this.props.input.onFocus(extracted); }; - handleAdd = (newValue) => { + handleAdd = newValue => { const values = [...this.state.values, newValue]; this.setState({ values }); this.handleChange(values); }; - handleDelete = (newValue) => { - const values = this.state.values.filter(v => (v.value !== newValue)); + handleDelete = newValue => { + const values = this.state.values.filter(v => v.value !== newValue); this.setState({ values }); this.handleChange(values); }; - handleChange = (eventOrValue) => { + handleChange = eventOrValue => { const extracted = this.extractIds(eventOrValue); this.props.onChange(extracted); this.props.input.onChange(extracted); }; - extractIds = (eventOrValue) => { - const value = (eventOrValue.target && eventOrValue.target.value) ? eventOrValue.target.value : eventOrValue; + extractIds = eventOrValue => { + const value = + eventOrValue.target && eventOrValue.target.value + ? eventOrValue.target.value + : eventOrValue; if (Array.isArray(value)) { return value.map(o => o.value); } @@ -116,20 +125,36 @@ export class SelectArrayInput extends Component { throw Error('Value of SelectArrayInput should be an array'); } return values - .map(value => choices.find(c => c[optionValue] === value) || { [optionValue]: value, [optionText]: value }) + .map( + value => + choices.find(c => c[optionValue] === value) || { + [optionValue]: value, + [optionText]: value, + } + ) .map(this.formatChoice); }; formatChoices = choices => choices.map(this.formatChoice); - formatChoice = (choice) => { - const { optionText, optionValue, translateChoice, translate } = this.props; - const choiceText = typeof optionText === 'function' ? optionText(choice) : get(choice, optionText); + formatChoice = choice => { + const { + optionText, + optionValue, + translateChoice, + translate, + } = this.props; + const choiceText = + typeof optionText === 'function' + ? optionText(choice) + : get(choice, optionText); return { value: get(choice, optionValue), - text: translateChoice ? translate(choiceText, { _: choiceText }) : choiceText, + text: translateChoice + ? translate(choiceText, { _: choiceText }) + : choiceText, }; - } + }; render() { const { @@ -163,7 +188,14 @@ export class SelectArrayInput extends Component { onRequestAdd={this.handleAdd} onRequestDelete={this.handleDelete} onUpdateInput={setFilter} - floatingLabelText={<FieldTitle label={label} source={source} resource={resource} isRequired={isRequired} />} + floatingLabelText={ + <FieldTitle + label={label} + source={source} + resource={resource} + isRequired={isRequired} + /> + } errorText={touched && error} style={elStyle} dataSource={this.formatChoices(choices)} @@ -189,10 +221,8 @@ SelectArrayInput.propTypes = { onFocus: PropTypes.func, setFilter: PropTypes.func, options: PropTypes.object, - optionText: PropTypes.oneOfType([ - PropTypes.string, - PropTypes.func, - ]).isRequired, + optionText: PropTypes.oneOfType([PropTypes.string, PropTypes.func]) + .isRequired, optionValue: PropTypes.string.isRequired, resource: PropTypes.string, source: PropTypes.string, diff --git a/src/mui/input/SelectArrayInput.spec.js b/src/mui/input/SelectArrayInput.spec.js index 3a634aa8..2eb94937 100644 --- a/src/mui/input/SelectArrayInput.spec.js +++ b/src/mui/input/SelectArrayInput.spec.js @@ -18,20 +18,27 @@ describe('<SelectArrayInput />', () => { }); it('should use the input parameter value as the initial input value', () => { - const wrapper = shallow(<SelectArrayInput {...defaultProps} input={{ value: [1, 2] }} />); + const wrapper = shallow( + <SelectArrayInput {...defaultProps} input={{ value: [1, 2] }} /> + ); const ChipInputElement = wrapper.find('ChipInput').first(); - assert.deepEqual(ChipInputElement.prop('value'), [{ value: 1, text: 1 }, { value: 2, text: 2 }]); + assert.deepEqual(ChipInputElement.prop('value'), [ + { value: 1, text: 1 }, + { value: 2, text: 2 }, + ]); }); it('should pass choices to the ChipInput as dataSource', () => { - const wrapper = shallow(<SelectArrayInput - {...defaultProps} - choices={[ - { id: 1, name: 'Book' }, - { id: 2, name: 'Video' }, - { id: 3, name: 'Audio' }, - ]} - />); + const wrapper = shallow( + <SelectArrayInput + {...defaultProps} + choices={[ + { id: 1, name: 'Book' }, + { id: 2, name: 'Video' }, + { id: 3, name: 'Audio' }, + ]} + /> + ); const ChipInputElement = wrapper.find('ChipInput').first(); assert.deepEqual(ChipInputElement.prop('dataSource'), [ { value: 1, text: 'Book' }, @@ -41,15 +48,17 @@ describe('<SelectArrayInput />', () => { }); it('should use the dataSource to set the initial input value', () => { - const wrapper = shallow(<SelectArrayInput - {...defaultProps} - input={{ value: [1, 2] }} - choices={[ - { id: 1, name: 'Book' }, - { id: 2, name: 'Video' }, - { id: 3, name: 'Audio' }, - ]} - />); + const wrapper = shallow( + <SelectArrayInput + {...defaultProps} + input={{ value: [1, 2] }} + choices={[ + { id: 1, name: 'Book' }, + { id: 2, name: 'Video' }, + { id: 3, name: 'Audio' }, + ]} + /> + ); const ChipInputElement = wrapper.find('ChipInput').first(); assert.deepEqual(ChipInputElement.prop('value'), [ { value: 1, text: 'Book' }, @@ -59,10 +68,9 @@ describe('<SelectArrayInput />', () => { it('should update the value when the dataSource updates', () => { const input = { value: [1, 2] }; - const wrapper = shallow(<SelectArrayInput - {...defaultProps} - input={input} - />); + const wrapper = shallow( + <SelectArrayInput {...defaultProps} input={input} /> + ); wrapper.setProps({ ...defaultProps, choices: [ @@ -80,13 +88,13 @@ describe('<SelectArrayInput />', () => { }); it('should use optionValue as value identifier', () => { - const wrapper = shallow(<SelectArrayInput - {...defaultProps} - optionValue="foobar" - choices={[ - { foobar: 'B', name: 'Book' }, - ]} - />); + const wrapper = shallow( + <SelectArrayInput + {...defaultProps} + optionValue="foobar" + choices={[{ foobar: 'B', name: 'Book' }]} + /> + ); const ChipInputElement = wrapper.find('ChipInput').first(); assert.deepEqual(ChipInputElement.prop('dataSource'), [ { value: 'B', text: 'Book' }, @@ -94,13 +102,13 @@ describe('<SelectArrayInput />', () => { }); it('should use optionValue including as value identifier', () => { - const wrapper = shallow(<SelectArrayInput - {...defaultProps} - optionValue="foobar.id" - choices={[ - { foobar: { id: 'B' }, name: 'Book' }, - ]} - />); + const wrapper = shallow( + <SelectArrayInput + {...defaultProps} + optionValue="foobar.id" + choices={[{ foobar: { id: 'B' }, name: 'Book' }]} + /> + ); const ChipInputElement = wrapper.find('ChipInput').first(); assert.deepEqual(ChipInputElement.prop('dataSource'), [ { value: 'B', text: 'Book' }, @@ -108,13 +116,13 @@ describe('<SelectArrayInput />', () => { }); it('should use optionText with a string value as text identifier', () => { - const wrapper = shallow(<SelectArrayInput - {...defaultProps} - optionText="foobar" - choices={[ - { id: 'B', foobar: 'Book' }, - ]} - />); + const wrapper = shallow( + <SelectArrayInput + {...defaultProps} + optionText="foobar" + choices={[{ id: 'B', foobar: 'Book' }]} + /> + ); const ChipInputElement = wrapper.find('ChipInput').first(); assert.deepEqual(ChipInputElement.prop('dataSource'), [ { value: 'B', text: 'Book' }, @@ -122,13 +130,13 @@ describe('<SelectArrayInput />', () => { }); it('should use optionText with a string value including "." as text identifier', () => { - const wrapper = shallow(<SelectArrayInput - {...defaultProps} - optionText="foobar.name" - choices={[ - { id: 'B', foobar: { name: 'Book' } }, - ]} - />); + const wrapper = shallow( + <SelectArrayInput + {...defaultProps} + optionText="foobar.name" + choices={[{ id: 'B', foobar: { name: 'Book' } }]} + /> + ); const ChipInputElement = wrapper.find('ChipInput').first(); assert.deepEqual(ChipInputElement.prop('dataSource'), [ { value: 'B', text: 'Book' }, @@ -136,13 +144,13 @@ describe('<SelectArrayInput />', () => { }); it('should use optionText with a function value as text identifier', () => { - const wrapper = shallow(<SelectArrayInput - {...defaultProps} - optionText={choice => choice.foobar} - choices={[ - { id: 'B', foobar: 'Book' }, - ]} - />); + const wrapper = shallow( + <SelectArrayInput + {...defaultProps} + optionText={choice => choice.foobar} + choices={[{ id: 'B', foobar: 'Book' }]} + /> + ); const ChipInputElement = wrapper.find('ChipInput').first(); assert.deepEqual(ChipInputElement.prop('dataSource'), [ { value: 'B', text: 'Book' }, @@ -150,15 +158,17 @@ describe('<SelectArrayInput />', () => { }); it('should translate the choices by default', () => { - const wrapper = shallow(<SelectArrayInput - {...defaultProps} - choices={[ - { id: 1, name: 'Book' }, - { id: 2, name: 'Video' }, - { id: 3, name: 'Audio' }, - ]} - translate={x => `**${x}**`} - />); + const wrapper = shallow( + <SelectArrayInput + {...defaultProps} + choices={[ + { id: 1, name: 'Book' }, + { id: 2, name: 'Video' }, + { id: 3, name: 'Audio' }, + ]} + translate={x => `**${x}**`} + /> + ); const ChipInputElement = wrapper.find('ChipInput').first(); assert.deepEqual(ChipInputElement.prop('dataSource'), [ { value: 1, text: '**Book**' }, @@ -168,16 +178,18 @@ describe('<SelectArrayInput />', () => { }); it('should not translate the choices if translateChoice is false', () => { - const wrapper = shallow(<SelectArrayInput - {...defaultProps} - choices={[ - { id: 1, name: 'Book' }, - { id: 2, name: 'Video' }, - { id: 3, name: 'Audio' }, - ]} - translate={x => `**${x}**`} - translateChoice={false} - />); + const wrapper = shallow( + <SelectArrayInput + {...defaultProps} + choices={[ + { id: 1, name: 'Book' }, + { id: 2, name: 'Video' }, + { id: 3, name: 'Audio' }, + ]} + translate={x => `**${x}**`} + translateChoice={false} + /> + ); const ChipInputElement = wrapper.find('ChipInput').first(); assert.deepEqual(ChipInputElement.prop('dataSource'), [ { value: 1, text: 'Book' }, @@ -188,19 +200,31 @@ describe('<SelectArrayInput />', () => { describe('error message', () => { it('should not be displayed if field is pristine', () => { - const wrapper = shallow(<SelectArrayInput {...defaultProps} meta={{ touched: false }} />); + const wrapper = shallow( + <SelectArrayInput {...defaultProps} meta={{ touched: false }} /> + ); const ChipInputElement = wrapper.find('ChipInput'); assert.equal(ChipInputElement.prop('errorText'), false); }); it('should not be displayed if field has been touched but is valid', () => { - const wrapper = shallow(<SelectArrayInput {...defaultProps} meta={{ touched: true, error: false }} />); + const wrapper = shallow( + <SelectArrayInput + {...defaultProps} + meta={{ touched: true, error: false }} + /> + ); const ChipInputElement = wrapper.find('ChipInput'); assert.equal(ChipInputElement.prop('errorText'), false); }); it('should be displayed if field has been touched and is invalid', () => { - const wrapper = shallow(<SelectArrayInput {...defaultProps} meta={{ touched: true, error: 'Required field.' }} />); + const wrapper = shallow( + <SelectArrayInput + {...defaultProps} + meta={{ touched: true, error: 'Required field.' }} + /> + ); const ChipInputElement = wrapper.find('ChipInput'); assert.equal(ChipInputElement.prop('errorText'), 'Required field.'); }); diff --git a/src/mui/input/SelectInput.js b/src/mui/input/SelectInput.js index 30306649..eaeffce3 100644 --- a/src/mui/input/SelectInput.js +++ b/src/mui/input/SelectInput.js @@ -71,7 +71,7 @@ export class SelectInput extends Component { */ state = { value: this.props.input.value, - } + }; componentWillReceiveProps(nextProps) { if (nextProps.input.value !== this.props.input.value) { @@ -82,9 +82,9 @@ export class SelectInput extends Component { handleChange = (event, index, value) => { this.props.input.onChange(value); this.setState({ value }); - } + }; - addAllowEmpty = (choices) => { + addAllowEmpty = choices => { if (this.props.allowEmpty) { return [ <MenuItem value={null} key="null" primaryText="" />, @@ -93,29 +93,34 @@ export class SelectInput extends Component { } return choices; - } + }; - renderMenuItem = (choice) => { + renderMenuItem = choice => { const { optionText, optionValue, translate, translateChoice, } = this.props; - const choiceName = React.isValidElement(optionText) ? // eslint-disable-line no-nested-ternary - React.cloneElement(optionText, { record: choice }) : - (typeof optionText === 'function' ? - optionText(choice) : - get(choice, optionText) - ); + const choiceName = React.isValidElement(optionText) // eslint-disable-line no-nested-ternary + ? React.cloneElement(optionText, { record: choice }) + : typeof optionText === 'function' + ? optionText(choice) + : get(choice, optionText); return ( <MenuItem key={get(choice, optionValue)} - primaryText={translateChoice ? translate(choiceName, { _: choiceName }) : choiceName} + primaryText={ + translateChoice ? ( + translate(choiceName, { _: choiceName }) + ) : ( + choiceName + ) + } value={get(choice, optionValue)} /> ); - } + }; render() { const { @@ -130,14 +135,23 @@ export class SelectInput extends Component { source, } = this.props; if (typeof meta === 'undefined') { - throw new Error('The SelectInput component wasn\'t called within a redux-form <Field>. Did you decorate it and forget to add the addField prop to your component? See https://marmelab.com/admin-on-rest/Inputs.html#writing-your-own-input-component for details.'); + throw new Error( + "The SelectInput component wasn't called within a redux-form <Field>. Did you decorate it and forget to add the addField prop to your component? See https://marmelab.com/admin-on-rest/Inputs.html#writing-your-own-input-component for details." + ); } const { touched, error } = meta; return ( <SelectField value={this.state.value} - floatingLabelText={<FieldTitle label={label} source={source} resource={resource} isRequired={isRequired} />} + floatingLabelText={ + <FieldTitle + label={label} + source={source} + resource={resource} + isRequired={isRequired} + /> + } onChange={this.handleChange} autoWidth style={elStyle} diff --git a/src/mui/input/SelectInput.spec.js b/src/mui/input/SelectInput.spec.js index c5dfec3b..4c63f4b7 100644 --- a/src/mui/input/SelectInput.spec.js +++ b/src/mui/input/SelectInput.spec.js @@ -12,26 +12,32 @@ describe('<SelectInput />', () => { }; it('should use a mui SelectField', () => { - const wrapper = shallow(<SelectInput {...defaultProps} input={{ value: 'hello' }} />); + const wrapper = shallow( + <SelectInput {...defaultProps} input={{ value: 'hello' }} /> + ); const SelectFieldElement = wrapper.find('SelectField'); assert.equal(SelectFieldElement.length, 1); assert.equal(SelectFieldElement.prop('value'), 'hello'); }); it('should use the input parameter value as the initial input value', () => { - const wrapper = shallow(<SelectInput {...defaultProps} input={{ value: 2 }} />); + const wrapper = shallow( + <SelectInput {...defaultProps} input={{ value: 2 }} /> + ); const SelectFieldElement = wrapper.find('SelectField').first(); assert.equal(SelectFieldElement.prop('value'), '2'); }); it('should render choices as mui MenuItem components', () => { - const wrapper = shallow(<SelectInput - {...defaultProps} - choices={[ - { id: 'M', name: 'Male' }, - { id: 'F', name: 'Female' }, - ]} - />); + const wrapper = shallow( + <SelectInput + {...defaultProps} + choices={[ + { id: 'M', name: 'Male' }, + { id: 'F', name: 'Female' }, + ]} + /> + ); const MenuItemElements = wrapper.find('MenuItem'); assert.equal(MenuItemElements.length, 2); const MenuItemElement1 = MenuItemElements.first(); @@ -43,14 +49,16 @@ describe('<SelectInput />', () => { }); it('should add an empty menu when allowEmpty is true', () => { - const wrapper = shallow(<SelectInput - allowEmpty - {...defaultProps} - choices={[ - { id: 'M', name: 'Male' }, - { id: 'F', name: 'Female' }, - ]} - />); + const wrapper = shallow( + <SelectInput + allowEmpty + {...defaultProps} + choices={[ + { id: 'M', name: 'Male' }, + { id: 'F', name: 'Female' }, + ]} + /> + ); const MenuItemElements = wrapper.find('MenuItem'); assert.equal(MenuItemElements.length, 3); const MenuItemElement1 = MenuItemElements.first(); @@ -72,13 +80,13 @@ describe('<SelectInput />', () => { }); it('should use optionValue as value identifier', () => { - const wrapper = shallow(<SelectInput - {...defaultProps} - optionValue="foobar" - choices={[ - { foobar: 'M', name: 'Male' }, - ]} - />); + const wrapper = shallow( + <SelectInput + {...defaultProps} + optionValue="foobar" + choices={[{ foobar: 'M', name: 'Male' }]} + /> + ); const MenuItemElements = wrapper.find('MenuItem'); const MenuItemElement1 = MenuItemElements.first(); assert.equal(MenuItemElement1.prop('value'), 'M'); @@ -86,13 +94,13 @@ describe('<SelectInput />', () => { }); it('should use optionValue including "." as value identifier', () => { - const wrapper = shallow(<SelectInput - {...defaultProps} - optionValue="foobar.id" - choices={[ - { foobar: { id: 'M' }, name: 'Male' }, - ]} - />); + const wrapper = shallow( + <SelectInput + {...defaultProps} + optionValue="foobar.id" + choices={[{ foobar: { id: 'M' }, name: 'Male' }]} + /> + ); const MenuItemElements = wrapper.find('MenuItem'); const MenuItemElement1 = MenuItemElements.first(); assert.equal(MenuItemElement1.prop('value'), 'M'); @@ -100,13 +108,13 @@ describe('<SelectInput />', () => { }); it('should use optionText with a string value as text identifier', () => { - const wrapper = shallow(<SelectInput - {...defaultProps} - optionText="foobar" - choices={[ - { id: 'M', foobar: 'Male' }, - ]} - />); + const wrapper = shallow( + <SelectInput + {...defaultProps} + optionText="foobar" + choices={[{ id: 'M', foobar: 'Male' }]} + /> + ); const MenuItemElements = wrapper.find('MenuItem'); const MenuItemElement1 = MenuItemElements.first(); assert.equal(MenuItemElement1.prop('value'), 'M'); @@ -114,13 +122,13 @@ describe('<SelectInput />', () => { }); it('should use optionText with a string value including "." as text identifier', () => { - const wrapper = shallow(<SelectInput - {...defaultProps} - optionText="foobar.name" - choices={[ - { id: 'M', foobar: { name: 'Male' } }, - ]} - />); + const wrapper = shallow( + <SelectInput + {...defaultProps} + optionText="foobar.name" + choices={[{ id: 'M', foobar: { name: 'Male' } }]} + /> + ); const MenuItemElements = wrapper.find('MenuItem'); const MenuItemElement1 = MenuItemElements.first(); assert.equal(MenuItemElement1.prop('value'), 'M'); @@ -128,13 +136,13 @@ describe('<SelectInput />', () => { }); it('should use optionText with a function value as text identifier', () => { - const wrapper = shallow(<SelectInput - {...defaultProps} - optionText={choice => choice.foobar} - choices={[ - { id: 'M', foobar: 'Male' }, - ]} - />); + const wrapper = shallow( + <SelectInput + {...defaultProps} + optionText={choice => choice.foobar} + choices={[{ id: 'M', foobar: 'Male' }]} + /> + ); const MenuItemElements = wrapper.find('MenuItem'); const MenuItemElement1 = MenuItemElements.first(); assert.equal(MenuItemElement1.prop('value'), 'M'); @@ -142,29 +150,37 @@ describe('<SelectInput />', () => { }); it('should use optionText with an element value as text identifier', () => { - const Foobar = ({ record }) => <span>{record.foobar}</span>; - const wrapper = shallow(<SelectInput - {...defaultProps} - optionText={<Foobar />} - choices={[ - { id: 'M', foobar: 'Male' }, - ]} - />); + const Foobar = ({ record }) => + <span> + {record.foobar} + </span>; + const wrapper = shallow( + <SelectInput + {...defaultProps} + optionText={<Foobar />} + choices={[{ id: 'M', foobar: 'Male' }]} + /> + ); const MenuItemElements = wrapper.find('MenuItem'); const MenuItemElement1 = MenuItemElements.first(); assert.equal(MenuItemElement1.prop('value'), 'M'); - assert.deepEqual(MenuItemElement1.prop('primaryText'), <Foobar record={{ id: 'M', foobar: 'Male' }} />); + assert.deepEqual( + MenuItemElement1.prop('primaryText'), + <Foobar record={{ id: 'M', foobar: 'Male' }} /> + ); }); it('should translate the choices by default', () => { - const wrapper = shallow(<SelectInput - {...defaultProps} - choices={[ - { id: 'M', name: 'Male' }, - { id: 'F', name: 'Female' }, - ]} - translate={x => `**${x}**`} - />); + const wrapper = shallow( + <SelectInput + {...defaultProps} + choices={[ + { id: 'M', name: 'Male' }, + { id: 'F', name: 'Female' }, + ]} + translate={x => `**${x}**`} + /> + ); const MenuItemElements = wrapper.find('MenuItem'); const MenuItemElement1 = MenuItemElements.first(); assert.equal(MenuItemElement1.prop('value'), 'M'); @@ -172,15 +188,17 @@ describe('<SelectInput />', () => { }); it('should not translate the choices if translateChoice is false', () => { - const wrapper = shallow(<SelectInput - {...defaultProps} - choices={[ - { id: 'M', name: 'Male' }, - { id: 'F', name: 'Female' }, - ]} - translate={x => `**${x}**`} - translateChoice={false} - />); + const wrapper = shallow( + <SelectInput + {...defaultProps} + choices={[ + { id: 'M', name: 'Male' }, + { id: 'F', name: 'Female' }, + ]} + translate={x => `**${x}**`} + translateChoice={false} + /> + ); const MenuItemElements = wrapper.find('MenuItem'); const MenuItemElement1 = MenuItemElements.first(); assert.equal(MenuItemElement1.prop('value'), 'M'); @@ -189,21 +207,36 @@ describe('<SelectInput />', () => { describe('error message', () => { it('should not be displayed if field is pristine', () => { - const wrapper = shallow(<SelectInput {...defaultProps} meta={{ touched: false }} />); + const wrapper = shallow( + <SelectInput {...defaultProps} meta={{ touched: false }} /> + ); const SelectFieldElement = wrapper.find('SelectField'); assert.equal(SelectFieldElement.prop('errorText'), false); }); it('should not be displayed if field has been touched but is valid', () => { - const wrapper = shallow(<SelectInput {...defaultProps} meta={{ touched: true, error: false }} />); + const wrapper = shallow( + <SelectInput + {...defaultProps} + meta={{ touched: true, error: false }} + /> + ); const SelectFieldElement = wrapper.find('SelectField'); assert.equal(SelectFieldElement.prop('errorText'), false); }); it('should be displayed if field has been touched and is invalid', () => { - const wrapper = shallow(<SelectInput {...defaultProps} meta={{ touched: true, error: 'Required field.' }} />); + const wrapper = shallow( + <SelectInput + {...defaultProps} + meta={{ touched: true, error: 'Required field.' }} + /> + ); const SelectFieldElement = wrapper.find('SelectField'); - assert.equal(SelectFieldElement.prop('errorText'), 'Required field.'); + assert.equal( + SelectFieldElement.prop('errorText'), + 'Required field.' + ); }); }); }); diff --git a/src/mui/input/TextInput.js b/src/mui/input/TextInput.js index d9ddd379..7f7d0ed7 100644 --- a/src/mui/input/TextInput.js +++ b/src/mui/input/TextInput.js @@ -18,20 +18,20 @@ import FieldTitle from '../../util/FieldTitle'; * The object passed as `options` props is passed to the material-ui <TextField> component */ export class TextInput extends Component { - handleBlur = (eventOrValue) => { + handleBlur = eventOrValue => { this.props.onBlur(eventOrValue); this.props.input.onBlur(eventOrValue); - } + }; - handleFocus = (event) => { + handleFocus = event => { this.props.onFocus(event); this.props.input.onFocus(event); - } + }; - handleChange = (eventOrValue) => { + handleChange = eventOrValue => { this.props.onChange(eventOrValue); this.props.input.onChange(eventOrValue); - } + }; render() { const { @@ -57,7 +57,14 @@ export class TextInput extends Component { onFocus={this.handleFocus} onChange={this.handleChange} type={type} - floatingLabelText={<FieldTitle label={label} source={source} resource={resource} isRequired={isRequired} />} + floatingLabelText={ + <FieldTitle + label={label} + source={source} + resource={resource} + isRequired={isRequired} + /> + } errorText={touched && error} style={elStyle} {...options} diff --git a/src/mui/input/TextInput.spec.js b/src/mui/input/TextInput.spec.js index 9ca926b6..adddc557 100644 --- a/src/mui/input/TextInput.spec.js +++ b/src/mui/input/TextInput.spec.js @@ -13,16 +13,19 @@ describe('<TextInput />', () => { }; it('should use a mui TextField', () => { - const wrapper = shallow(<TextInput {...defaultProps} input={{ value: 'hello' }} />); + const wrapper = shallow( + <TextInput {...defaultProps} input={{ value: 'hello' }} /> + ); const TextFieldElement = wrapper.find('TextField'); assert.equal(TextFieldElement.length, 1); assert.equal(TextFieldElement.prop('value'), 'hello'); assert.equal(TextFieldElement.prop('type'), 'text'); }); - it('should use a mui TextField', () => { - const wrapper = shallow(<TextInput {...defaultProps} type="password" />); + const wrapper = shallow( + <TextInput {...defaultProps} type="password" /> + ); const TextFieldElement = wrapper.find('TextField'); assert.equal(TextFieldElement.length, 1); assert.equal(TextFieldElement.prop('type'), 'password'); @@ -31,10 +34,7 @@ describe('<TextInput />', () => { it('should call redux-form onBlur handler when blurred', () => { const onBlur = sinon.spy(); const wrapper = shallow( - <TextInput - {...defaultProps} - input={{ onBlur }} - />, + <TextInput {...defaultProps} input={{ onBlur }} /> ); const TextFieldElement = wrapper.find('TextField').first(); @@ -44,19 +44,31 @@ describe('<TextInput />', () => { describe('error message', () => { it('should not be displayed if field is pristine', () => { - const wrapper = shallow(<TextInput {...defaultProps} meta={{ touched: false }} />); + const wrapper = shallow( + <TextInput {...defaultProps} meta={{ touched: false }} /> + ); const TextFieldElement = wrapper.find('TextField'); assert.equal(TextFieldElement.prop('errorText'), false); }); it('should not be displayed if field has been touched but is valid', () => { - const wrapper = shallow(<TextInput {...defaultProps} meta={{ touched: true, error: false }} />); + const wrapper = shallow( + <TextInput + {...defaultProps} + meta={{ touched: true, error: false }} + /> + ); const TextFieldElement = wrapper.find('TextField'); assert.equal(TextFieldElement.prop('errorText'), false); }); it('should be displayed if field has been touched and is invalid', () => { - const wrapper = shallow(<TextInput {...defaultProps} meta={{ touched: true, error: 'Required field.' }} />); + const wrapper = shallow( + <TextInput + {...defaultProps} + meta={{ touched: true, error: 'Required field.' }} + /> + ); const TextFieldElement = wrapper.find('TextField'); assert.equal(TextFieldElement.prop('errorText'), 'Required field.'); }); diff --git a/src/mui/layout/AppBar.js b/src/mui/layout/AppBar.js index 959c4489..cb32920e 100644 --- a/src/mui/layout/AppBar.js +++ b/src/mui/layout/AppBar.js @@ -6,18 +6,12 @@ import muiThemeable from 'material-ui/styles/muiThemeable'; import compose from 'recompose/compose'; import { toggleSidebar as toggleSidebarAction } from '../../actions'; -const AppBar = ({ title, toggleSidebar }) => ( - <MuiAppBar - title={title} - onLeftIconButtonTouchTap={toggleSidebar} - /> -); +const AppBar = ({ title, toggleSidebar }) => + <MuiAppBar title={title} onLeftIconButtonTouchTap={toggleSidebar} />; AppBar.propTypes = { - title: PropTypes.oneOfType([ - PropTypes.string, - PropTypes.element - ]).isRequired, + title: PropTypes.oneOfType([PropTypes.string, PropTypes.element]) + .isRequired, toggleSidebar: PropTypes.func.isRequired, }; @@ -25,7 +19,7 @@ const enhance = compose( muiThemeable(), // force redraw on theme change connect(null, { toggleSidebar: toggleSidebarAction, - }), + }) ); export default enhance(AppBar); diff --git a/src/mui/layout/AppBarMobile.js b/src/mui/layout/AppBarMobile.js index 53210f27..61b77008 100644 --- a/src/mui/layout/AppBarMobile.js +++ b/src/mui/layout/AppBarMobile.js @@ -28,10 +28,10 @@ const style = { }; class AppBarMobile extends Component { - handleLeftIconButtonTouchTap = (event) => { + handleLeftIconButtonTouchTap = event => { event.preventDefault(); this.props.toggleSidebar(); - } + }; render() { const { title } = this.props; @@ -48,10 +48,8 @@ class AppBarMobile extends Component { } AppBarMobile.propTypes = { - title: PropTypes.oneOfType([ - PropTypes.string, - PropTypes.element, - ]).isRequired, + title: PropTypes.oneOfType([PropTypes.string, PropTypes.element]) + .isRequired, toggleSidebar: PropTypes.func.isRequired, }; @@ -59,7 +57,7 @@ const enhance = compose( muiThemeable(), // force redraw on theme change connect(null, { toggleSidebar: toggleSidebarAction, - }), + }) ); export default enhance(AppBarMobile); diff --git a/src/mui/layout/DashboardMenuItem.js b/src/mui/layout/DashboardMenuItem.js index c49ae48f..a1e0b3e3 100644 --- a/src/mui/layout/DashboardMenuItem.js +++ b/src/mui/layout/DashboardMenuItem.js @@ -5,14 +5,13 @@ import DashboardIcon from 'material-ui/svg-icons/action/dashboard'; import { Link } from 'react-router-dom'; import translate from '../../i18n/translate'; -const DashboardMenuItem = ({ onTouchTap, translate }) => ( +const DashboardMenuItem = ({ onTouchTap, translate }) => <MenuItem containerElement={<Link to="/" />} primaryText={translate('aor.page.dashboard')} leftIcon={<DashboardIcon />} onTouchTap={onTouchTap} - /> -); + />; DashboardMenuItem.propTypes = { onTouchTap: PropTypes.func, diff --git a/src/mui/layout/Layout.js b/src/mui/layout/Layout.js index 8ff4dcd7..9a5a2f0b 100644 --- a/src/mui/layout/Layout.js +++ b/src/mui/layout/Layout.js @@ -92,9 +92,22 @@ class Layout extends Component { <MuiThemeProvider muiTheme={muiTheme}> <div style={prefixedStyles.wrapper}> <div style={prefixedStyles.main}> - { width !== 1 && <AppBar title={title} />} - <div className="body" style={width === 1 ? prefixedStyles.bodySmall : prefixedStyles.body}> - <div style={width === 1 ? prefixedStyles.contentSmall : prefixedStyles.content}> + {width !== 1 && <AppBar title={title} />} + <div + className="body" + style={ + width === 1 + ? prefixedStyles.bodySmall + : prefixedStyles.body + } + > + <div + style={ + width === 1 + ? prefixedStyles.contentSmall + : prefixedStyles.content + } + > <AdminRoutes customRoutes={customRoutes} resources={resources} @@ -107,13 +120,14 @@ class Layout extends Component { </Sidebar> </div> <Notification /> - {isLoading && <CircularProgress - className="app-loader" - color="#fff" - size={width === 1 ? 20 : 30} - thickness={2} - style={styles.loader} - />} + {isLoading && + <CircularProgress + className="app-loader" + color="#fff" + size={width === 1 ? 20 : 30} + thickness={2} + style={styles.loader} + />} </div> </div> </MuiThemeProvider> @@ -148,7 +162,7 @@ const enhance = compose( connect(mapStateToProps, { setSidebarVisibility: setSidebarVisibilityAction, }), - withWidth(), + withWidth() ); export default enhance(Layout); diff --git a/src/mui/layout/Menu.js b/src/mui/layout/Menu.js index 39361515..f019c6ed 100644 --- a/src/mui/layout/Menu.js +++ b/src/mui/layout/Menu.js @@ -19,12 +19,16 @@ const styles = { const translatedResourceName = (resource, translate) => translate(`resources.${resource.name}.name`, { smart_count: 2, - _: resource.options && resource.options.label ? - translate(resource.options.label, { smart_count: 2, _: resource.options.label }) : - inflection.humanize(inflection.pluralize(resource.name)), + _: + resource.options && resource.options.label + ? translate(resource.options.label, { + smart_count: 2, + _: resource.options.label, + }) + : inflection.humanize(inflection.pluralize(resource.name)), }); -const Menu = ({ hasDashboard, onMenuTap, resources, translate, logout }) => ( +const Menu = ({ hasDashboard, onMenuTap, resources, translate, logout }) => <div style={styles.main}> {hasDashboard && <DashboardMenuItem onTouchTap={onMenuTap} />} {resources @@ -36,12 +40,10 @@ const Menu = ({ hasDashboard, onMenuTap, resources, translate, logout }) => ( primaryText={translatedResourceName(resource, translate)} leftIcon={<resource.icon />} onTouchTap={onMenuTap} - />, - ) - } + /> + )} {logout} - </div> -); + </div>; Menu.propTypes = { hasDashboard: PropTypes.bool, @@ -55,9 +57,6 @@ Menu.defaultProps = { onMenuTap: () => null, }; -const enhance = compose( - pure, - translate, -); +const enhance = compose(pure, translate); export default enhance(Menu); diff --git a/src/mui/layout/MenuItemLink.js b/src/mui/layout/MenuItemLink.js index 07a8ca2a..9b326825 100644 --- a/src/mui/layout/MenuItemLink.js +++ b/src/mui/layout/MenuItemLink.js @@ -9,21 +9,22 @@ export class MenuItemLinkComponent extends Component { history: PropTypes.object.isRequired, onTouchTap: PropTypes.func.isRequired, to: PropTypes.string.isRequired, - } + }; handleMenuTap = () => { this.props.history.push(this.props.to); this.props.onTouchTap(); - } + }; render() { - const { history, match, location, staticContext, ...props } = this.props; // eslint-disable-line + const { + history, + match, + location, + staticContext, + ...props + } = this.props; // eslint-disable-line - return ( - <MenuItem - {...props} - onTouchTap={this.handleMenuTap} - /> - ); + return <MenuItem {...props} onTouchTap={this.handleMenuTap} />; } } diff --git a/src/mui/layout/Notification.js b/src/mui/layout/Notification.js index cbff4b45..07a03c14 100644 --- a/src/mui/layout/Notification.js +++ b/src/mui/layout/Notification.js @@ -2,20 +2,13 @@ import React from 'react'; import PropTypes from 'prop-types'; import { connect } from 'react-redux'; import Snackbar from 'material-ui/Snackbar'; -import { hideNotification as hideNotificationAction } from '../../actions/notificationActions' ; +import { hideNotification as hideNotificationAction } from '../../actions/notificationActions'; import translate from '../../i18n/translate'; function getStyles(context) { if (!context) return { primary1Color: '#00bcd4', accent1Color: '#ff4081' }; const { - muiTheme: { - baseTheme: { - palette: { - primary1Color, - accent1Color, - }, - }, - }, + muiTheme: { baseTheme: { palette: { primary1Color, accent1Color } } }, } = context; return { primary1Color, accent1Color }; } @@ -35,13 +28,15 @@ class Notification extends React.Component { if (type === 'confirm') { style.backgroundColor = primary1Color; } - return (<Snackbar - open={!!message} - message={!!message && translate(message)} - autoHideDuration={4000} - onRequestClose={this.handleRequestClose} - bodyStyle={style} - />); + return ( + <Snackbar + open={!!message} + message={!!message && translate(message)} + autoHideDuration={4000} + onRequestClose={this.handleRequestClose} + bodyStyle={style} + /> + ); } } @@ -65,7 +60,8 @@ const mapStateToProps = state => ({ type: state.admin.notification.type, }); -export default translate(connect( - mapStateToProps, - { hideNotification: hideNotificationAction }, -)(Notification)); +export default translate( + connect(mapStateToProps, { hideNotification: hideNotificationAction })( + Notification + ) +); diff --git a/src/mui/layout/Responsive.js b/src/mui/layout/Responsive.js index c8af4514..922ec09a 100644 --- a/src/mui/layout/Responsive.js +++ b/src/mui/layout/Responsive.js @@ -5,17 +5,17 @@ import withWidth from 'material-ui/utils/withWidth'; export const Responsive = ({ small, medium, large, width, ...rest }) => { let component; switch (width) { - case 1: - component = small ? small : (medium ? medium : large); - break; - case 2: - component = medium ? medium : (large ? large : small); - break; - case 3: - component = large ? large : (medium ? medium : small); - break; - default: - throw new Error(`Unknown width ${width}`); + case 1: + component = small ? small : medium ? medium : large; + break; + case 2: + component = medium ? medium : large ? large : small; + break; + case 3: + component = large ? large : medium ? medium : small; + break; + default: + throw new Error(`Unknown width ${width}`); } return React.cloneElement(component, rest); }; diff --git a/src/mui/layout/Responsive.spec.js b/src/mui/layout/Responsive.spec.js index 727d5670..62c8bf00 100644 --- a/src/mui/layout/Responsive.spec.js +++ b/src/mui/layout/Responsive.spec.js @@ -10,47 +10,110 @@ describe('<Responsive>', () => { const Large = () => <div />; it('should render the small component on small screens', () => { - const wrapper = shallow(<Responsive small={<Small />} medium={<Medium />} large={<Large />} width={1} />); + const wrapper = shallow( + <Responsive + small={<Small />} + medium={<Medium />} + large={<Large />} + width={1} + /> + ); const component = wrapper.find('Small'); assert.equal(component.length, 1); }); it('should render the medium component on medium screens', () => { - const wrapper = shallow(<Responsive small={<Small />} medium={<Medium />} large={<Large />} width={2} />); + const wrapper = shallow( + <Responsive + small={<Small />} + medium={<Medium />} + large={<Large />} + width={2} + /> + ); const component = wrapper.find('Medium'); assert.equal(component.length, 1); }); it('should render the large component on large screens', () => { - const wrapper = shallow(<Responsive small={<Small />} medium={<Medium />} large={<Large />} width={3} />); + const wrapper = shallow( + <Responsive + small={<Small />} + medium={<Medium />} + large={<Large />} + width={3} + /> + ); const component = wrapper.find('Large'); assert.equal(component.length, 1); }); it('should render the small component on all screens when no other component is passed', () => { - assert.equal(shallow(<Responsive small={<Small />} width={1} />).find('Small').length, 1); - assert.equal(shallow(<Responsive small={<Small />} width={2} />).find('Small').length, 1); - assert.equal(shallow(<Responsive small={<Small />} width={3} />).find('Small').length, 1); + assert.equal( + shallow(<Responsive small={<Small />} width={1} />).find('Small') + .length, + 1 + ); + assert.equal( + shallow(<Responsive small={<Small />} width={2} />).find('Small') + .length, + 1 + ); + assert.equal( + shallow(<Responsive small={<Small />} width={3} />).find('Small') + .length, + 1 + ); }); it('should render the medium component on all screens when no other component is passed', () => { - assert.equal(shallow(<Responsive medium={<Medium />} width={1} />).find('Medium').length, 1); - assert.equal(shallow(<Responsive medium={<Medium />} width={2} />).find('Medium').length, 1); - assert.equal(shallow(<Responsive medium={<Medium />} width={3} />).find('Medium').length, 1); + assert.equal( + shallow(<Responsive medium={<Medium />} width={1} />).find('Medium') + .length, + 1 + ); + assert.equal( + shallow(<Responsive medium={<Medium />} width={2} />).find('Medium') + .length, + 1 + ); + assert.equal( + shallow(<Responsive medium={<Medium />} width={3} />).find('Medium') + .length, + 1 + ); }); it('should render the large component on all screens when no other component is passed', () => { - assert.equal(shallow(<Responsive large={<Large />} width={1} />).find('Large').length, 1); - assert.equal(shallow(<Responsive large={<Large />} width={2} />).find('Large').length, 1); - assert.equal(shallow(<Responsive large={<Large />} width={3} />).find('Large').length, 1); + assert.equal( + shallow(<Responsive large={<Large />} width={1} />).find('Large') + .length, + 1 + ); + assert.equal( + shallow(<Responsive large={<Large />} width={2} />).find('Large') + .length, + 1 + ); + assert.equal( + shallow(<Responsive large={<Large />} width={3} />).find('Large') + .length, + 1 + ); }); it('should fallback to the large component on medium screens', () => { - const wrapper = shallow(<Responsive small={<Small />} large={<Large />} width={2} />); + const wrapper = shallow( + <Responsive small={<Small />} large={<Large />} width={2} /> + ); const component = wrapper.find('Large'); assert.equal(component.length, 1); }); it('should fallback to the medium component on small screens', () => { - const wrapper = shallow(<Responsive medium={<Medium />} large={<Large />} width={1} />); + const wrapper = shallow( + <Responsive medium={<Medium />} large={<Large />} width={1} /> + ); const component = wrapper.find('Medium'); assert.equal(component.length, 1); }); it('should fallback to the medium component on large screens', () => { - const wrapper = shallow(<Responsive small={<Small />} medium={<Medium />} width={3} />); + const wrapper = shallow( + <Responsive small={<Small />} medium={<Medium />} width={3} /> + ); const component = wrapper.find('Medium'); assert.equal(component.length, 1); }); diff --git a/src/mui/layout/Sidebar.js b/src/mui/layout/Sidebar.js index 2c8cacd8..5e0aef87 100644 --- a/src/mui/layout/Sidebar.js +++ b/src/mui/layout/Sidebar.js @@ -14,7 +14,7 @@ const getWidth = width => (typeof width === 'number' ? `${width}px` : width); const getStyles = ({ drawer }) => { const width = drawer && drawer.width ? getWidth(drawer.width) : '16em'; - return ({ + return { sidebarOpen: { flex: `0 0 ${width}`, marginLeft: 0, @@ -27,7 +27,7 @@ const getStyles = ({ drawer }) => { order: -1, transition: 'margin 450ms cubic-bezier(0.23, 1, 0.32, 1) 0ms', }, - }); + }; }; // We shouldn't need PureComponent here as it's connected @@ -35,7 +35,7 @@ const getStyles = ({ drawer }) => { class Sidebar extends PureComponent { handleClose = () => { this.props.setSidebarVisibility(false); - } + }; render() { const { open, setSidebarVisibility, children, muiTheme } = this.props; @@ -44,13 +44,23 @@ class Sidebar extends PureComponent { return ( <Responsive small={ - <Drawer docked={false} open={open} onRequestChange={setSidebarVisibility}> - {React.cloneElement(children, { onMenuTap: this.handleClose })} + <Drawer + docked={false} + open={open} + onRequestChange={setSidebarVisibility} + > + {React.cloneElement(children, { + onMenuTap: this.handleClose, + })} </Drawer> } medium={ - <Paper style={open ? styles.sidebarOpen : styles.sidebarClosed}> - {React.cloneElement(children, { onMenuTap: () => null })} + <Paper + style={open ? styles.sidebarOpen : styles.sidebarClosed} + > + {React.cloneElement(children, { + onMenuTap: () => null, + })} </Paper> } /> @@ -73,5 +83,7 @@ const mapStateToProps = (state, props) => ({ export default compose( muiThemeable(), - connect(mapStateToProps, { setSidebarVisibility: setSidebarVisibilityAction }), + connect(mapStateToProps, { + setSidebarVisibility: setSidebarVisibilityAction, + }) )(Sidebar); diff --git a/src/mui/layout/Title.js b/src/mui/layout/Title.js index 52a0f365..30cf66ff 100644 --- a/src/mui/layout/Title.js +++ b/src/mui/layout/Title.js @@ -6,10 +6,18 @@ import translate from '../../i18n/translate'; const Title = ({ defaultTitle, record, title, translate }) => { if (!title) { - return <span>{defaultTitle}</span>; + return ( + <span> + {defaultTitle} + </span> + ); } if (typeof title === 'string') { - return <span>{translate(title, { _: title })}</span>; + return ( + <span> + {translate(title, { _: title })} + </span> + ); } return React.cloneElement(title, { record }); }; @@ -18,15 +26,12 @@ Title.propTypes = { defaultTitle: PropTypes.string.isRequired, record: PropTypes.object, translate: PropTypes.func.isRequired, - title: PropTypes.oneOfType([ - PropTypes.string, - PropTypes.element, - ]), + title: PropTypes.oneOfType([PropTypes.string, PropTypes.element]), }; const enhance = compose( translate, - onlyUpdateForKeys('defaultTitle', 'record', 'title'), + onlyUpdateForKeys('defaultTitle', 'record', 'title') ); export default enhance(Title); diff --git a/src/mui/layout/ViewTitle.js b/src/mui/layout/ViewTitle.js index b2620b40..3130cf3d 100644 --- a/src/mui/layout/ViewTitle.js +++ b/src/mui/layout/ViewTitle.js @@ -3,10 +3,9 @@ import { CardTitle } from 'material-ui/Card'; import withWidth from 'material-ui/utils/withWidth'; import AppBarMobile from './AppBarMobile'; -const ViewTitle = ({ title, width }) => ( +const ViewTitle = ({ title, width }) => width === 1 ? <AppBarMobile title={title} /> - : <CardTitle title={title} className="title" /> -); + : <CardTitle title={title} className="title" />; export default withWidth()(ViewTitle); diff --git a/src/mui/list/Actions.js b/src/mui/list/Actions.js index 22633782..7665167b 100644 --- a/src/mui/list/Actions.js +++ b/src/mui/list/Actions.js @@ -13,17 +13,40 @@ const cardActionStyle = { flexWrap: 'wrap', }; -const Actions = ({ resource, filters, displayedFilters, filterValues, theme, hasCreate, basePath, showFilter, refresh }) => { +const Actions = ({ + resource, + filters, + displayedFilters, + filterValues, + theme, + hasCreate, + basePath, + showFilter, + refresh, +}) => { const muiTheme = getMuiTheme(theme); const prefix = autoprefixer(muiTheme); return ( <CardActions style={prefix(cardActionStyle)}> - {filters && React.cloneElement(filters, { resource, showFilter, displayedFilters, filterValues, context: 'button' }) } + {filters && + React.cloneElement(filters, { + resource, + showFilter, + displayedFilters, + filterValues, + context: 'button', + })} {hasCreate && <CreateButton basePath={basePath} />} <RefreshButton refresh={refresh} /> </CardActions> ); }; -export default onlyUpdateForKeys(['resource', 'filters', 'displayedFilters', 'filterValues', 'theme'])(Actions); +export default onlyUpdateForKeys([ + 'resource', + 'filters', + 'displayedFilters', + 'filterValues', + 'theme', +])(Actions); diff --git a/src/mui/list/Datagrid.js b/src/mui/list/Datagrid.js index 46378a7f..4edc2d5c 100644 --- a/src/mui/list/Datagrid.js +++ b/src/mui/list/Datagrid.js @@ -1,7 +1,14 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; import muiThemeable from 'material-ui/styles/muiThemeable'; -import { Table, TableBody, TableHeader, TableHeaderColumn, TableRow, TableRowColumn } from 'material-ui/Table'; +import { + Table, + TableBody, + TableHeader, + TableHeaderColumn, + TableRow, + TableRowColumn, +} from 'material-ui/Table'; import DatagridCell from './DatagridCell'; import DatagridHeaderCell from './DatagridHeaderCell'; import DatagridBody from './DatagridBody'; @@ -71,31 +78,70 @@ const defaultStyles = { * </ReferenceManyField> */ class Datagrid extends Component { - updateSort = (event) => { + updateSort = event => { event.stopPropagation(); this.props.setSort(event.currentTarget.dataset.sort); - } + }; render() { - const { resource, children, ids, isLoading, data, currentSort, basePath, styles = defaultStyles, muiTheme, rowStyle, options, headerOptions, bodyOptions, rowOptions } = this.props; + const { + resource, + children, + ids, + isLoading, + data, + currentSort, + basePath, + styles = defaultStyles, + muiTheme, + rowStyle, + options, + headerOptions, + bodyOptions, + rowOptions, + } = this.props; return ( - <Table style={options && options.fixedHeader ? null : styles.table} fixedHeader={false} {...options}> - <TableHeader displaySelectAll={false} adjustForCheckbox={false} {...headerOptions}> + <Table + style={options && options.fixedHeader ? null : styles.table} + fixedHeader={false} + {...options} + > + <TableHeader + displaySelectAll={false} + adjustForCheckbox={false} + {...headerOptions} + > <TableRow style={muiTheme.tableRow}> - {React.Children.map(children, (field, index) => ( + {React.Children.map(children, (field, index) => <DatagridHeaderCell key={field.props.source || index} field={field} - defaultStyle={index === 0 ? styles.header['th:first-child'] : styles.header.th} + defaultStyle={ + index === 0 + ? styles.header['th:first-child'] + : styles.header.th + } currentSort={currentSort} - isSorting={field.props.source === currentSort.field} + isSorting={ + field.props.source === currentSort.field + } updateSort={this.updateSort} resource={resource} /> - ))} + )} </TableRow> </TableHeader> - <DatagridBody resource={resource} ids={ids} data={data} basePath={basePath} styles={styles} rowStyle={rowStyle} isLoading={isLoading} options={bodyOptions} rowOptions={rowOptions}> + <DatagridBody + resource={resource} + ids={ids} + data={data} + basePath={basePath} + styles={styles} + rowStyle={rowStyle} + isLoading={isLoading} + options={bodyOptions} + rowOptions={rowOptions} + > {children} </DatagridBody> </Table> diff --git a/src/mui/list/DatagridBody.js b/src/mui/list/DatagridBody.js index 730d1a44..6fb333b0 100644 --- a/src/mui/list/DatagridBody.js +++ b/src/mui/list/DatagridBody.js @@ -4,23 +4,47 @@ import shouldUpdate from 'recompose/shouldUpdate'; import { TableBody, TableRow } from 'material-ui/Table'; import DatagridCell from './DatagridCell'; -const DatagridBody = ({ resource, children, ids, data, basePath, styles, rowStyle, options, rowOptions, ...rest }) => ( - <TableBody displayRowCheckbox={false} className="datagrid-body" {...rest} {...options}> - {ids.map((id, rowIndex) => ( - <TableRow style={rowStyle ? rowStyle(data[id], rowIndex) : styles.tr} key={id} selectable={false} {...rowOptions}> - {React.Children.map(children, (field, index) => ( +const DatagridBody = ({ + resource, + children, + ids, + data, + basePath, + styles, + rowStyle, + options, + rowOptions, + ...rest +}) => + <TableBody + displayRowCheckbox={false} + className="datagrid-body" + {...rest} + {...options} + > + {ids.map((id, rowIndex) => + <TableRow + style={rowStyle ? rowStyle(data[id], rowIndex) : styles.tr} + key={id} + selectable={false} + {...rowOptions} + > + {React.Children.map(children, (field, index) => <DatagridCell key={`${id}-${field.props.source || index}`} className={`column-${field.props.source}`} record={data[id]} - defaultStyle={index === 0 ? styles.cell['td:first-child'] : styles.cell.td} + defaultStyle={ + index === 0 + ? styles.cell['td:first-child'] + : styles.cell.td + } {...{ field, basePath, resource }} /> - ))} + )} </TableRow> - ))} - </TableBody> -); + )} + </TableBody>; DatagridBody.propTypes = { ids: PropTypes.arrayOf(PropTypes.any).isRequired, @@ -39,7 +63,9 @@ DatagridBody.defaultProps = { ids: [], }; -const PureDatagridBody = shouldUpdate((props, nextProps) => nextProps.isLoading === false)(DatagridBody); +const PureDatagridBody = shouldUpdate( + (props, nextProps) => nextProps.isLoading === false +)(DatagridBody); // trick material-ui Table into thinking this is one of the child type it supports PureDatagridBody.muiName = 'TableBody'; diff --git a/src/mui/list/DatagridCell.js b/src/mui/list/DatagridCell.js index aa832b54..fc081ce4 100644 --- a/src/mui/list/DatagridCell.js +++ b/src/mui/list/DatagridCell.js @@ -3,8 +3,23 @@ import PropTypes from 'prop-types'; import defaultsDeep from 'lodash.defaultsdeep'; import { TableRowColumn } from 'material-ui/Table'; -const DatagridCell = ({ className, field, record, basePath, resource, style, defaultStyle, ...rest }) => { - const computedStyle = defaultsDeep({}, style, field.props.style, field.type.defaultProps ? field.type.defaultProps.style : {}, defaultStyle); +const DatagridCell = ({ + className, + field, + record, + basePath, + resource, + style, + defaultStyle, + ...rest +}) => { + const computedStyle = defaultsDeep( + {}, + style, + field.props.style, + field.type.defaultProps ? field.type.defaultProps.style : {}, + defaultStyle + ); return ( <TableRowColumn className={className} style={computedStyle} {...rest}> {React.cloneElement(field, { record, basePath, resource })} diff --git a/src/mui/list/DatagridCell.spec.js b/src/mui/list/DatagridCell.spec.js index b0d25280..fbbbdfeb 100644 --- a/src/mui/list/DatagridCell.spec.js +++ b/src/mui/list/DatagridCell.spec.js @@ -6,17 +6,28 @@ import DatagridCell from './DatagridCell'; describe('<DatagridCell />', () => { it('should render as a mui <TableRowColumn /> component', () => { - const wrapper = shallow(<DatagridCell field={{ type: 'foo', props: {} }} />); + const wrapper = shallow( + <DatagridCell field={{ type: 'foo', props: {} }} /> + ); const col = wrapper.find('TableRowColumn'); assert.equal(col.length, 1); }); it('should use regular col style by default', () => { - const wrapper = shallow(<DatagridCell field={{ type: 'foo', props: {} }} defaultStyle={{ color: 'blue' }} />); + const wrapper = shallow( + <DatagridCell + field={{ type: 'foo', props: {} }} + defaultStyle={{ color: 'blue' }} + /> + ); const col = wrapper.find('TableRowColumn'); assert.deepEqual(col.at(0).prop('style'), { color: 'blue' }); }); it('should use field style to override default style', () => { - const wrapper = shallow(<DatagridCell field={{ type: 'foo', props: { style: { color: 'red' } } }} />); + const wrapper = shallow( + <DatagridCell + field={{ type: 'foo', props: { style: { color: 'red' } } }} + /> + ); const col = wrapper.find('TableRowColumn'); assert.deepEqual(col.at(0).prop('style'), { color: 'red' }); }); diff --git a/src/mui/list/DatagridHeaderCell.js b/src/mui/list/DatagridHeaderCell.js index ed7a96f3..d306a54c 100644 --- a/src/mui/list/DatagridHeaderCell.js +++ b/src/mui/list/DatagridHeaderCell.js @@ -23,26 +23,55 @@ const styles = { }, }; -export const DatagridHeaderCell = ({ field, defaultStyle, currentSort, updateSort, resource }) => { - const style = defaultsDeep({}, field.props.headerStyle, field.type.defaultProps ? field.type.defaultProps.headerStyle : {}, defaultStyle); +export const DatagridHeaderCell = ({ + field, + defaultStyle, + currentSort, + updateSort, + resource, +}) => { + const style = defaultsDeep( + {}, + field.props.headerStyle, + field.type.defaultProps ? field.type.defaultProps.headerStyle : {}, + defaultStyle + ); return ( <TableHeaderColumn style={style}> - {field.props.sortable !== false && field.props.source ? - <FlatButton - labelPosition="before" - onClick={updateSort} - data-sort={field.props.source} - label={<FieldTitle label={field.props.label} source={field.props.source} resource={resource} />} - icon={field.props.source === currentSort.field ? - <ContentSort style={currentSort.order === 'ASC' ? { transform: 'rotate(180deg)' } : {}} /> : false - } - style={styles.sortButton} - /> - : - <span style={styles.nonSortableLabel}> - {<FieldTitle label={field.props.label} source={field.props.source} resource={resource} />} - </span> - } + {field.props.sortable !== false && field.props.source + ? <FlatButton + labelPosition="before" + onClick={updateSort} + data-sort={field.props.source} + label={ + <FieldTitle + label={field.props.label} + source={field.props.source} + resource={resource} + /> + } + icon={ + field.props.source === currentSort.field + ? <ContentSort + style={ + currentSort.order === 'ASC' + ? { transform: 'rotate(180deg)' } + : {} + } + /> + : false + } + style={styles.sortButton} + /> + : <span style={styles.nonSortableLabel}> + { + <FieldTitle + label={field.props.label} + source={field.props.source} + resource={resource} + /> + } + </span>} </TableHeaderColumn> ); }; @@ -65,7 +94,9 @@ DatagridHeaderCell.propTypes = { updateSort: PropTypes.func.isRequired, }; -export default shouldUpdate((props, nextProps) => - props.isSorting !== nextProps.isSorting - || (nextProps.isSorting && props.currentSort.order !== nextProps.currentSort.order), +export default shouldUpdate( + (props, nextProps) => + props.isSorting !== nextProps.isSorting || + (nextProps.isSorting && + props.currentSort.order !== nextProps.currentSort.order) )(DatagridHeaderCell); diff --git a/src/mui/list/DatagridHeaderCell.spec.js b/src/mui/list/DatagridHeaderCell.spec.js index 4ef1eef5..58d36af4 100644 --- a/src/mui/list/DatagridHeaderCell.spec.js +++ b/src/mui/list/DatagridHeaderCell.spec.js @@ -13,7 +13,7 @@ describe('<DatagridHeaderCell />', () => { }; it('should be enabled when field has a source', () => { - const wrapper = shallow(( + const wrapper = shallow( <DatagridHeaderCell currentSort={{}} field={{ @@ -23,24 +23,24 @@ describe('<DatagridHeaderCell />', () => { }, }} /> - )); + ); assert.equal(wrapper.find('FlatButton').length, 1); }); it('should be disabled when field has no source', () => { - const wrapper = shallow(( + const wrapper = shallow( <DatagridHeaderCell currentSort={{}} field={{ ...defaultField }} /> - )); + ); assert.equal(wrapper.find('FlatButton').length, 0); }); it('should be disabled when sortable prop is explicitly set to false', () => { - const wrapper = shallow(( + const wrapper = shallow( <DatagridHeaderCell currentSort={{}} field={{ @@ -51,7 +51,7 @@ describe('<DatagridHeaderCell />', () => { }, }} /> - )); + ); assert.equal(wrapper.find('FlatButton').length, 0); }); diff --git a/src/mui/list/Filter.js b/src/mui/list/Filter.js index db7a7d56..c14949da 100644 --- a/src/mui/list/Filter.js +++ b/src/mui/list/Filter.js @@ -23,22 +23,29 @@ class Filter extends Component { } } - setFilters = debounce((filters) => { - if (!shallowEqual(filters, this.filters)) { // fix for redux-form bug with onChange and enableReinitialize + setFilters = debounce(filters => { + if (!shallowEqual(filters, this.filters)) { + // fix for redux-form bug with onChange and enableReinitialize const filtersWithoutEmpty = filters; - Object.keys(filtersWithoutEmpty).forEach((filterName) => { + Object.keys(filtersWithoutEmpty).forEach(filterName => { if (filtersWithoutEmpty[filterName] === '') { // remove empty filter from query delete filtersWithoutEmpty[filterName]; } - }) + }); this.props.setFilters(filtersWithoutEmpty); this.filters = filtersWithoutEmpty; } - }, this.props.debounce) + }, this.props.debounce); renderButton() { - const { resource, children, showFilter, displayedFilters, filterValues } = this.props; + const { + resource, + children, + showFilter, + displayedFilters, + filterValues, + } = this.props; return ( <FilterButton resource={resource} @@ -51,7 +58,14 @@ class Filter extends Component { } renderForm() { - const { resource, children, hideFilter, displayedFilters, filterValues, theme } = this.props; + const { + resource, + children, + hideFilter, + displayedFilters, + filterValues, + theme, + } = this.props; return ( <FilterForm resource={resource} @@ -66,7 +80,9 @@ class Filter extends Component { } render() { - return this.props.context === 'button' ? this.renderButton() : this.renderForm(); + return this.props.context === 'button' + ? this.renderButton() + : this.renderForm(); } } diff --git a/src/mui/list/Filter.spec.js b/src/mui/list/Filter.spec.js index 0c58108d..46f27856 100644 --- a/src/mui/list/Filter.spec.js +++ b/src/mui/list/Filter.spec.js @@ -18,7 +18,9 @@ describe('<Filter />', () => { }); it('should pass `filterValues` as `initialValues` props', () => { - const wrapper = shallow(<Filter {...defaultProps} filterValues={{ q: 'Lorem' }} />); + const wrapper = shallow( + <Filter {...defaultProps} filterValues={{ q: 'Lorem' }} /> + ); const form = wrapper.find('getContext(ReduxForm)').first(); assert.deepEqual(form.prop('initialValues'), { q: 'Lorem' }); }); diff --git a/src/mui/list/FilterButton.js b/src/mui/list/FilterButton.js index 20dda96f..52da2ce9 100644 --- a/src/mui/list/FilterButton.js +++ b/src/mui/list/FilterButton.js @@ -21,12 +21,12 @@ export class FilterButton extends Component { getHiddenFilters() { const { filters, displayedFilters, filterValues } = this.props; - return filters - .filter(filterElement => + return filters.filter( + filterElement => !filterElement.props.alwaysOn && !displayedFilters[filterElement.props.source] && - !filterValues[filterElement.props.source], - ); + !filterValues[filterElement.props.source] + ); } handleTouchTap(event) { @@ -57,33 +57,36 @@ export class FilterButton extends Component { const { resource } = this.props; const { open, anchorEl } = this.state; - return (hiddenFilters.length > 0 && <div style={{ display: 'inline-block' }}> - <FlatButton - className="add-filter" - primary - label={this.props.translate('aor.action.add_filter')} - icon={<ContentFilter />} - onTouchTap={this.handleTouchTap} - /> - <Popover - open={open} - anchorEl={anchorEl} - anchorOrigin={{ horizontal: 'left', vertical: 'bottom' }} - targetOrigin={{ horizontal: 'left', vertical: 'top' }} - onRequestClose={this.handleRequestClose} - > - <Menu> - {hiddenFilters.map(filterElement => - <FilterButtonMenuItem - key={filterElement.props.source} - filter={filterElement.props} - resource={resource} - onShow={this.handleShow} - />, - )} - </Menu> - </Popover> - </div>); + return ( + hiddenFilters.length > 0 && + <div style={{ display: 'inline-block' }}> + <FlatButton + className="add-filter" + primary + label={this.props.translate('aor.action.add_filter')} + icon={<ContentFilter />} + onTouchTap={this.handleTouchTap} + /> + <Popover + open={open} + anchorEl={anchorEl} + anchorOrigin={{ horizontal: 'left', vertical: 'bottom' }} + targetOrigin={{ horizontal: 'left', vertical: 'top' }} + onRequestClose={this.handleRequestClose} + > + <Menu> + {hiddenFilters.map(filterElement => + <FilterButtonMenuItem + key={filterElement.props.source} + filter={filterElement.props} + resource={resource} + onShow={this.handleShow} + /> + )} + </Menu> + </Popover> + </div> + ); } } diff --git a/src/mui/list/FilterButtonMenuItem.js b/src/mui/list/FilterButtonMenuItem.js index a98cfb75..0de53df3 100644 --- a/src/mui/list/FilterButtonMenuItem.js +++ b/src/mui/list/FilterButtonMenuItem.js @@ -8,12 +8,12 @@ class FilterButtonMenuItem extends Component { filter: PropTypes.object.isRequired, onShow: PropTypes.func.isRequired, resource: PropTypes.string.isRequired, - } + }; handleShow = () => { const { filter, onShow } = this.props; onShow({ source: filter.source, defaultValue: filter.defaultValue }); - } + }; render() { const { filter, resource } = this.props; @@ -24,7 +24,13 @@ class FilterButtonMenuItem extends Component { data-key={filter.source} data-default-value={filter.defaultValue} key={filter.source} - primaryText={<FieldTitle label={filter.label} source={filter.source} resource={resource} />} + primaryText={ + <FieldTitle + label={filter.label} + source={filter.source} + resource={resource} + /> + } onTouchTap={this.handleShow} /> ); diff --git a/src/mui/list/FilterForm.js b/src/mui/list/FilterForm.js index 749031cf..331a280f 100644 --- a/src/mui/list/FilterForm.js +++ b/src/mui/list/FilterForm.js @@ -12,7 +12,14 @@ import translate from '../../i18n/translate'; import defaultTheme from '../defaultTheme'; const styles = { - card: { marginTop: '-14px', paddingTop: 0, display: 'flex', justifyContent: 'flex-end', alignItems: 'flex-end', flexWrap: 'wrap' }, + card: { + marginTop: '-14px', + paddingTop: 0, + display: 'flex', + justifyContent: 'flex-end', + alignItems: 'flex-end', + flexWrap: 'wrap', + }, body: { display: 'flex', alignItems: 'flex-end' }, spacer: { width: 48 }, icon: { color: '#00bcd4', paddingBottom: 0 }, @@ -24,56 +31,64 @@ const emptyRecord = {}; export class FilterForm extends Component { getShownFilters() { const { filters, displayedFilters, initialValues } = this.props; - return filters - .filter(filterElement => + return filters.filter( + filterElement => filterElement.props.alwaysOn || displayedFilters[filterElement.props.source] || - typeof initialValues[filterElement.props.source] !== 'undefined', - ); + typeof initialValues[filterElement.props.source] !== 'undefined' + ); } - handleHide = event => this.props.hideFilter(event.currentTarget.dataset.key); + handleHide = event => + this.props.hideFilter(event.currentTarget.dataset.key); render() { const { resource, translate, theme } = this.props; const muiTheme = getMuiTheme(theme); const prefix = autoprefixer(muiTheme); - return (<div> - <CardText style={prefix(styles.card)}> - {this.getShownFilters().reverse().map(filterElement => - <div - key={filterElement.props.source} - data-source={filterElement.props.source} - className="filter-field" - style={filterElement.props.style || prefix(styles.body)} - > - {filterElement.props.alwaysOn ? - <div style={prefix(styles.spacer)}> </div> : - <IconButton - iconStyle={prefix(styles.icon)} - className="hide-filter" - onTouchTap={this.handleHide} - data-key={filterElement.props.source} - tooltip={translate('aor.action.remove_filter')} - > - <ActionHide /> - </IconButton> - } - <div> - <Field - allowEmpty - {...filterElement.props} - name={filterElement.props.source} - component={filterElement.type} - resource={resource} - record={emptyRecord} - /> + return ( + <div> + <CardText style={prefix(styles.card)}> + {this.getShownFilters().reverse().map(filterElement => + <div + key={filterElement.props.source} + data-source={filterElement.props.source} + className="filter-field" + style={ + filterElement.props.style || prefix(styles.body) + } + > + {filterElement.props.alwaysOn + ? <div style={prefix(styles.spacer)}> +   + </div> + : <IconButton + iconStyle={prefix(styles.icon)} + className="hide-filter" + onTouchTap={this.handleHide} + data-key={filterElement.props.source} + tooltip={translate( + 'aor.action.remove_filter' + )} + > + <ActionHide /> + </IconButton>} + <div> + <Field + allowEmpty + {...filterElement.props} + name={filterElement.props.source} + component={filterElement.type} + resource={resource} + record={emptyRecord} + /> + </div> </div> - </div>, - )} - </CardText> - <div style={prefix(styles.clearFix)} /> - </div>); + )} + </CardText> + <div style={prefix(styles.clearFix)} /> + </div> + ); } } @@ -97,7 +112,7 @@ const enhance = compose( form: 'filterForm', enableReinitialize: true, onChange: (values, dispatch, props) => props.setFilters(values), - }), + }) ); export default enhance(FilterForm); diff --git a/src/mui/list/FilterForm.spec.js b/src/mui/list/FilterForm.spec.js index 0b2e1422..f914b685 100644 --- a/src/mui/list/FilterForm.spec.js +++ b/src/mui/list/FilterForm.spec.js @@ -11,7 +11,7 @@ import TextInput from '../input/TextInput'; try { require('react-tap-event-plugin')(); -} catch(e) { +} catch (e) { // already loaded, probably in watch mode // do nothing } diff --git a/src/mui/list/List.js b/src/mui/list/List.js index 44b5aea6..2d41815b 100644 --- a/src/mui/list/List.js +++ b/src/mui/list/List.js @@ -9,7 +9,12 @@ import { createSelector } from 'reselect'; import inflection from 'inflection'; import getMuiTheme from 'material-ui/styles/getMuiTheme'; import autoprefixer from 'material-ui/utils/autoprefixer'; -import queryReducer, { SET_SORT, SET_PAGE, SET_FILTER, SORT_DESC } from '../../reducer/resource/list/queryReducer'; +import queryReducer, { + SET_SORT, + SET_PAGE, + SET_FILTER, + SORT_DESC, +} from '../../reducer/resource/list/queryReducer'; import ViewTitle from '../layout/ViewTitle'; import Title from '../layout/Title'; import DefaultPagination from './Pagination'; @@ -83,12 +88,18 @@ export class List extends Component { } componentWillReceiveProps(nextProps) { - if (nextProps.resource !== this.props.resource - || nextProps.query.sort !== this.props.query.sort - || nextProps.query.order !== this.props.query.order - || nextProps.query.page !== this.props.query.page - || nextProps.query.filter !== this.props.query.filter) { - this.updateData(Object.keys(nextProps.query).length > 0 ? nextProps.query : nextProps.params); + if ( + nextProps.resource !== this.props.resource || + nextProps.query.sort !== this.props.query.sort || + nextProps.query.order !== this.props.query.order || + nextProps.query.page !== this.props.query.page || + nextProps.query.filter !== this.props.query.filter + ) { + this.updateData( + Object.keys(nextProps.query).length > 0 + ? nextProps.query + : nextProps.params + ); } if (nextProps.data !== this.props.data && this.fullRefresh) { this.fullRefresh = false; @@ -98,9 +109,10 @@ export class List extends Component { shouldComponentUpdate(nextProps, nextState) { if ( - nextProps.isLoading === this.props.isLoading - && nextProps.width === this.props.width - && nextState === this.state) { + nextProps.isLoading === this.props.isLoading && + nextProps.width === this.props.width && + nextState === this.state + ) { return false; } return true; @@ -110,11 +122,11 @@ export class List extends Component { return this.props.location.pathname; } - refresh = (event) => { + refresh = event => { event.stopPropagation(); this.fullRefresh = true; this.updateData(); - } + }; /** * Merge list params from 3 different sources: @@ -123,7 +135,10 @@ export class List extends Component { * - the props passed to the List component */ getQuery() { - const query = Object.keys(this.props.query).length > 0 ? this.props.query : { ...this.props.params }; + const query = + Object.keys(this.props.query).length > 0 + ? this.props.query + : { ...this.props.params }; if (!query.sort) { query.sort = this.props.sort.field; query.order = this.props.sort.order; @@ -137,38 +152,70 @@ export class List extends Component { updateData(query) { const params = query || this.getQuery(); const { sort, order, page, perPage, filter } = params; - const pagination = { page: parseInt(page, 10), perPage: parseInt(perPage, 10) }; + const pagination = { + page: parseInt(page, 10), + perPage: parseInt(perPage, 10), + }; const permanentFilter = this.props.filter; - this.props.crudGetList(this.props.resource, pagination, { field: sort, order }, { ...filter, ...permanentFilter }); + this.props.crudGetList( + this.props.resource, + pagination, + { field: sort, order }, + { ...filter, ...permanentFilter } + ); } setSort = sort => this.changeParams({ type: SET_SORT, payload: sort }); setPage = page => this.changeParams({ type: SET_PAGE, payload: page }); - setFilters = filters => this.changeParams({ type: SET_FILTER, payload: filters }); + setFilters = filters => + this.changeParams({ type: SET_FILTER, payload: filters }); showFilter = (filterName, defaultValue) => { this.setState({ [filterName]: true }); if (typeof defaultValue !== 'undefined') { - this.setFilters({ ...this.props.filterValues, [filterName]: defaultValue }); + this.setFilters({ + ...this.props.filterValues, + [filterName]: defaultValue, + }); } - } + }; - hideFilter = (filterName) => { + hideFilter = filterName => { this.setState({ [filterName]: false }); const newFilters = removeKey(this.props.filterValues, filterName); this.setFilters(newFilters); - } + }; changeParams(action) { const newParams = queryReducer(this.getQuery(), action); - this.props.push({ ...this.props.location, search: `?${stringify({ ...newParams, filter: JSON.stringify(newParams.filter) })}` }); + this.props.push({ + ...this.props.location, + search: `?${stringify({ + ...newParams, + filter: JSON.stringify(newParams.filter), + })}`, + }); this.props.changeListParams(this.props.resource, newParams); } render() { - const { filters, pagination = <DefaultPagination />, actions = <DefaultActions />, resource, hasCreate, title, data, ids, total, children, isLoading, translate, theme } = this.props; + const { + filters, + pagination = <DefaultPagination />, + actions = <DefaultActions />, + resource, + hasCreate, + title, + data, + ids, + total, + children, + isLoading, + translate, + theme, + } = this.props; const { key } = this.state; const query = this.getQuery(); const filterValues = query.filter; @@ -178,57 +225,68 @@ export class List extends Component { smart_count: 2, _: inflection.humanize(inflection.pluralize(resource)), }); - const defaultTitle = translate('aor.page.list', { name: `${resourceName}` }); - const titleElement = <Title title={title} defaultTitle={defaultTitle} />; + const defaultTitle = translate('aor.page.list', { + name: `${resourceName}`, + }); + const titleElement = ( + <Title title={title} defaultTitle={defaultTitle} /> + ); const muiTheme = getMuiTheme(theme); const prefix = autoprefixer(muiTheme); return ( <div className="list-page"> <Card style={{ opacity: isLoading ? 0.8 : 1 }}> - <div style={prefix(styles.header)} > + <div style={prefix(styles.header)}> <ViewTitle title={titleElement} /> - {actions && React.cloneElement(actions, { + {actions && + React.cloneElement(actions, { + resource, + filters, + filterValues, + basePath, + hasCreate, + displayedFilters: this.state, + showFilter: this.showFilter, + refresh: this.refresh, + theme, + })} + </div> + {filters && + React.cloneElement(filters, { resource, - filters, + hideFilter: this.hideFilter, filterValues, - basePath, - hasCreate, displayedFilters: this.state, - showFilter: this.showFilter, - refresh: this.refresh, - theme, + setFilters: this.setFilters, + context: 'form', })} - </div> - {filters && React.cloneElement(filters, { - resource, - hideFilter: this.hideFilter, - filterValues, - displayedFilters: this.state, - setFilters: this.setFilters, - context: 'form', - })} - { isLoading || total > 0 ? - <div key={key}> - {children && React.cloneElement(children, { - resource, - ids, - data, - currentSort: { field: query.sort, order: query.order }, - basePath, - isLoading, - setSort: this.setSort, - })} - { pagination && React.cloneElement(pagination, { - total, - page: parseInt(query.page, 10), - perPage: parseInt(query.perPage, 10), - setPage: this.setPage, - }) } - </div> - : - <CardText style={styles.noResults}>{translate('aor.navigation.no_results')}</CardText> - } + {isLoading || total > 0 + ? <div key={key}> + {children && + React.cloneElement(children, { + resource, + ids, + data, + currentSort: { + field: query.sort, + order: query.order, + }, + basePath, + isLoading, + setSort: this.setSort, + })} + {pagination && + React.cloneElement(pagination, { + total, + page: parseInt(query.page, 10), + perPage: parseInt(query.perPage, 10), + setPage: this.setPage, + })} + </div> + : <CardText style={styles.noResults}> + {translate('aor.navigation.no_results')} + </CardText>} </Card> </div> ); @@ -279,16 +337,13 @@ List.defaultProps = { }; const getLocationSearch = props => props.location.search; -const getQuery = createSelector( - getLocationSearch, - (locationSearch) => { - const query = parse(locationSearch); - if (query.filter && typeof query.filter === 'string') { - query.filter = JSON.parse(query.filter); - } - return query; - }, -); +const getQuery = createSelector(getLocationSearch, locationSearch => { + const query = parse(locationSearch); + if (query.filter && typeof query.filter === 'string') { + query.filter = JSON.parse(query.filter); + } + return query; +}); function mapStateToProps(state, props) { const resourceState = state.admin[props.resource]; @@ -304,15 +359,12 @@ function mapStateToProps(state, props) { } const enhance = compose( - connect( - mapStateToProps, - { - crudGetList: crudGetListAction, - changeListParams: changeListParamsAction, - push: pushAction, - }, - ), - translate, + connect(mapStateToProps, { + crudGetList: crudGetListAction, + changeListParams: changeListParamsAction, + push: pushAction, + }), + translate ); export default enhance(List); diff --git a/src/mui/list/List.spec.js b/src/mui/list/List.spec.js index b868f72c..8c0f1aa1 100644 --- a/src/mui/list/List.spec.js +++ b/src/mui/list/List.spec.js @@ -43,7 +43,7 @@ describe('<List />', () => { <List {...defaultProps} translate={x => x} - children={[ 'not_empty' ]} + children={['not_empty']} total={1} changeFormValue={() => true} changeListParams={() => true} diff --git a/src/mui/list/Pagination.js b/src/mui/list/Pagination.js index f3507e9b..79368ba1 100644 --- a/src/mui/list/Pagination.js +++ b/src/mui/list/Pagination.js @@ -47,13 +47,13 @@ export class Pagination extends Component { if (page < nbPages) { input.push(page + 1); } - if (page === (nbPages - 3)) { + if (page === nbPages - 3) { input.push(nbPages - 1); } - if (page < (nbPages - 3)) { + if (page < nbPages - 3) { input.push('.'); } - if (page < (nbPages - 1)) { + if (page < nbPages - 1) { input.push(nbPages); } @@ -64,36 +64,57 @@ export class Pagination extends Component { return Math.ceil(this.props.total / this.props.perPage) || 1; } - prevPage = (event) => { + prevPage = event => { event.stopPropagation(); if (this.props.page === 1) { - throw new Error(this.props.translate('aor.navigation.page_out_from_begin')); + throw new Error( + this.props.translate('aor.navigation.page_out_from_begin') + ); } this.props.setPage(this.props.page - 1); - } + }; - nextPage = (event) => { + nextPage = event => { event.stopPropagation(); if (this.props.page > this.getNbPages()) { - throw new Error(this.props.translate('aor.navigation.page_out_from_end')); + throw new Error( + this.props.translate('aor.navigation.page_out_from_end') + ); } this.props.setPage(this.props.page + 1); - } + }; - gotoPage = (event) => { + gotoPage = event => { event.stopPropagation(); const page = event.currentTarget.dataset.page; if (page < 1 || page > this.getNbPages()) { - throw new Error(this.props.translate('aor.navigation.page_out_of_boundaries', { page })); + throw new Error( + this.props.translate('aor.navigation.page_out_of_boundaries', { + page, + }) + ); } this.props.setPage(page); - } + }; renderPageNums() { - return this.range().map((pageNum, index) => - (pageNum === '.') ? - <span key={`hyphen_${index}`} style={{ padding: '1.2em' }}>…</span> : - <FlatButton className="page-number" key={pageNum} label={pageNum} data-page={pageNum} onClick={this.gotoPage} primary={pageNum !== this.props.page} style={styles.button} />, + return this.range().map( + (pageNum, index) => + pageNum === '.' ? ( + <span key={`hyphen_${index}`} style={{ padding: '1.2em' }}> + … + </span> + ) : ( + <FlatButton + className="page-number" + key={pageNum} + label={pageNum} + data-page={pageNum} + onClick={this.gotoPage} + primary={pageNum !== this.props.page} + style={styles.button} + /> + ) ); } @@ -107,35 +128,68 @@ export class Pagination extends Component { return width === 1 ? ( <Toolbar> <ToolbarGroup style={styles.mobileToolbar}> - {page > 1 && + {page > 1 && ( <IconButton onClick={this.prevPage}> - <ChevronLeft color={muiTheme.palette.primary1Color} /> + <ChevronLeft + color={muiTheme.palette.primary1Color} + /> </IconButton> - } - <span style={styles.pageInfo}>{translate('aor.navigation.page_range_info', { offsetBegin, offsetEnd, total })}</span> - {page !== nbPages && + )} + <span style={styles.pageInfo}> + {translate('aor.navigation.page_range_info', { + offsetBegin, + offsetEnd, + total, + })} + </span> + {page !== nbPages && ( <IconButton onClick={this.nextPage}> - <ChevronRight color={muiTheme.palette.primary1Color} /> + <ChevronRight + color={muiTheme.palette.primary1Color} + /> </IconButton> - } + )} </ToolbarGroup> </Toolbar> ) : ( <Toolbar> <ToolbarGroup firstChild> - <span className="displayed-records" style={styles.pageInfo}>{translate('aor.navigation.page_range_info', { offsetBegin, offsetEnd, total })}</span> + <span className="displayed-records" style={styles.pageInfo}> + {translate('aor.navigation.page_range_info', { + offsetBegin, + offsetEnd, + total, + })} + </span> </ToolbarGroup> - {nbPages > 1 && + {nbPages > 1 && ( <ToolbarGroup> - {page > 1 && - <FlatButton className="previous-page" primary key="prev" label={translate('aor.navigation.prev')} icon={<ChevronLeft />} onClick={this.prevPage} style={styles.button} /> - } - {this.renderPageNums()} - {page !== nbPages && - <FlatButton className="next-page" primary key="next" label={translate('aor.navigation.next')} icon={<ChevronRight />} labelPosition="before" onClick={this.nextPage} style={styles.button} /> - } + {page > 1 && ( + <FlatButton + className="previous-page" + primary + key="prev" + label={translate('aor.navigation.prev')} + icon={<ChevronLeft />} + onClick={this.prevPage} + style={styles.button} + /> + )} + {this.renderPageNums()} + {page !== nbPages && ( + <FlatButton + className="next-page" + primary + key="next" + label={translate('aor.navigation.next')} + icon={<ChevronRight />} + labelPosition="before" + onClick={this.nextPage} + style={styles.button} + /> + )} </ToolbarGroup> - } + )} </Toolbar> ); } @@ -151,11 +205,6 @@ Pagination.propTypes = { width: PropTypes.number, }; -const enhance = compose( - pure, - translate, - withWidth(), - muiThemeable(), -); +const enhance = compose(pure, translate, withWidth(), muiThemeable()); export default enhance(Pagination); diff --git a/src/mui/list/Pagination.spec.js b/src/mui/list/Pagination.spec.js index 260a27d2..2a3b7389 100644 --- a/src/mui/list/Pagination.spec.js +++ b/src/mui/list/Pagination.spec.js @@ -8,14 +8,32 @@ describe('<Pagination />', () => { const muiTheme = { palette: {} }; describe('mobile', () => { it('should render a condensed <Toolbar>', () => { - const wrapper = shallow(<Pagination muiTheme={muiTheme} page={2} perPage={5} total={15} translate={x => x} width={1} />); + const wrapper = shallow( + <Pagination + muiTheme={muiTheme} + page={2} + perPage={5} + total={15} + translate={x => x} + width={1} + /> + ); const iconButtons = wrapper.find('IconButton'); assert.equal(iconButtons.length, 2); const flatButtons = wrapper.find('FlatButton'); assert.equal(flatButtons.length, 0); }); it('should render only the text when no pagination is necessary', () => { - const wrapper = shallow(<Pagination muiTheme={muiTheme} page={1} perPage={20} total={15} translate={x => x} width={1} />); + const wrapper = shallow( + <Pagination + muiTheme={muiTheme} + page={1} + perPage={20} + total={15} + translate={x => x} + width={1} + /> + ); const iconButtons = wrapper.find('IconButton'); assert.equal(iconButtons.length, 0); const span = wrapper.find('span'); @@ -24,14 +42,32 @@ describe('<Pagination />', () => { }); describe('desktop', () => { it('should render a normal <Toolbar>', () => { - const wrapper = shallow(<Pagination muiTheme={muiTheme} page={2} perPage={5} total={15} translate={x => x} width={2} />); + const wrapper = shallow( + <Pagination + muiTheme={muiTheme} + page={2} + perPage={5} + total={15} + translate={x => x} + width={2} + /> + ); const iconButtons = wrapper.find('IconButton'); assert.equal(iconButtons.length, 0); const flatButtons = wrapper.find('FlatButton'); assert.equal(flatButtons.length, 5); }); it('should render only the text when no pagination is necessary', () => { - const wrapper = shallow(<Pagination muiTheme={muiTheme} page={1} perPage={20} total={15} translate={x => x} width={2} />); + const wrapper = shallow( + <Pagination + muiTheme={muiTheme} + page={1} + perPage={20} + total={15} + translate={x => x} + width={2} + /> + ); const flatButtons = wrapper.find('FlatButton'); assert.equal(flatButtons.length, 0); const span = wrapper.find('span'); diff --git a/src/mui/list/SimpleList.js b/src/mui/list/SimpleList.js index 805ab2fd..565395e3 100644 --- a/src/mui/list/SimpleList.js +++ b/src/mui/list/SimpleList.js @@ -17,9 +17,9 @@ const SimpleList = ({ leftIcon, rightAvatar, rightIcon, -}) => ( +}) => <List> - {ids.map(id => ( + {ids.map(id => <ListItem key={id} primaryText={ @@ -28,8 +28,7 @@ const SimpleList = ({ {tertiaryText && <span style={tertiaryStyle}> {tertiaryText(data[id], id)} - </span> - } + </span>} </div> } secondaryText={secondaryText && secondaryText(data[id], id)} @@ -40,9 +39,8 @@ const SimpleList = ({ rightIcon={rightIcon && rightIcon(data[id], id)} containerElement={<Link to={`${basePath}/${id}`} />} /> - ))} - </List> -); + )} + </List>; SimpleList.propTypes = { ids: PropTypes.array, diff --git a/src/mui/list/SingleFieldList.js b/src/mui/list/SingleFieldList.js index 29a870dc..a896e003 100644 --- a/src/mui/list/SingleFieldList.js +++ b/src/mui/list/SingleFieldList.js @@ -11,7 +11,7 @@ import PropTypes from 'prop-types'; * </SingleFieldList> * </ReferenceManyField> */ -const SingleFieldList = ({ ids, data, resource, basePath, children }) => ( +const SingleFieldList = ({ ids, data, resource, basePath, children }) => <div style={{ display: 'flex', flexWrap: 'wrap' }}> {ids.map(id => React.cloneElement(children, { @@ -21,8 +21,7 @@ const SingleFieldList = ({ ids, data, resource, basePath, children }) => ( basePath, }) )} - </div> -); + </div>; SingleFieldList.propTypes = { children: PropTypes.element.isRequired, diff --git a/src/reducer/index.js b/src/reducer/index.js index 3523e64b..3ea1209c 100644 --- a/src/reducer/index.js +++ b/src/reducer/index.js @@ -6,10 +6,13 @@ import references from './references'; import saving from './saving'; import ui from './ui'; -export default (resources) => { +export default resources => { const resourceReducers = {}; - resources.forEach((resource) => { - resourceReducers[resource.name] = resourceReducer(resource.name, resource.options); + resources.forEach(resource => { + resourceReducers[resource.name] = resourceReducer( + resource.name, + resource.options + ); }); return combineReducers({ ...resourceReducers, diff --git a/src/reducer/loading.js b/src/reducer/loading.js index 53b9aedd..58ade724 100644 --- a/src/reducer/loading.js +++ b/src/reducer/loading.js @@ -1,18 +1,27 @@ -import { FETCH_START, FETCH_END, FETCH_ERROR, FETCH_CANCEL } from '../actions/fetchActions'; -import { USER_LOGIN_LOADING, USER_LOGIN_SUCCESS, USER_LOGIN_FAILURE } from '../actions/authActions'; +import { + FETCH_START, + FETCH_END, + FETCH_ERROR, + FETCH_CANCEL, +} from '../actions/fetchActions'; +import { + USER_LOGIN_LOADING, + USER_LOGIN_SUCCESS, + USER_LOGIN_FAILURE, +} from '../actions/authActions'; export default (previousState = 0, { type }) => { switch (type) { - case FETCH_START: - case USER_LOGIN_LOADING: - return previousState + 1; - case FETCH_END: - case FETCH_ERROR: - case FETCH_CANCEL: - case USER_LOGIN_SUCCESS: - case USER_LOGIN_FAILURE: - return Math.max(previousState - 1, 0); - default: - return previousState; + case FETCH_START: + case USER_LOGIN_LOADING: + return previousState + 1; + case FETCH_END: + case FETCH_ERROR: + case FETCH_CANCEL: + case USER_LOGIN_SUCCESS: + case USER_LOGIN_FAILURE: + return Math.max(previousState - 1, 0); + default: + return previousState; } }; diff --git a/src/reducer/loading.spec.js b/src/reducer/loading.spec.js index 59565e78..57e17cdf 100644 --- a/src/reducer/loading.spec.js +++ b/src/reducer/loading.spec.js @@ -1,6 +1,15 @@ import assert from 'assert'; -import { FETCH_START, FETCH_END, FETCH_ERROR, FETCH_CANCEL } from '../actions/fetchActions'; -import { USER_LOGIN_LOADING, USER_LOGIN_SUCCESS, USER_LOGIN_FAILURE } from '../actions/authActions'; +import { + FETCH_START, + FETCH_END, + FETCH_ERROR, + FETCH_CANCEL, +} from '../actions/fetchActions'; +import { + USER_LOGIN_LOADING, + USER_LOGIN_SUCCESS, + USER_LOGIN_FAILURE, +} from '../actions/authActions'; import reducer from './loading'; describe('loading reducer', () => { diff --git a/src/reducer/locale.js b/src/reducer/locale.js index 72b94cbf..f069966a 100644 --- a/src/reducer/locale.js +++ b/src/reducer/locale.js @@ -2,12 +2,13 @@ import { DEFAULT_LOCALE } from '../i18n/index'; import { CHANGE_LOCALE } from '../actions/localeActions'; export default (initialLocale = DEFAULT_LOCALE) => ( - (previousLocale = initialLocale, { type, payload }) => { - switch (type) { + previousLocale = initialLocale, + { type, payload } +) => { + switch (type) { case CHANGE_LOCALE: return payload; default: return previousLocale; - } } -); +}; diff --git a/src/reducer/locale.spec.js b/src/reducer/locale.spec.js index c5a2e14e..c393a43c 100644 --- a/src/reducer/locale.spec.js +++ b/src/reducer/locale.spec.js @@ -9,6 +9,9 @@ describe('locale reducer', () => { assert.equal(DEFAULT_LOCALE, reducer()(undefined, {})); }); it('should change with CHANGE_LOCALE actions', () => { - assert.equal('fr', reducer()('en', { type: CHANGE_LOCALE, payload: 'fr' })); + assert.equal( + 'fr', + reducer()('en', { type: CHANGE_LOCALE, payload: 'fr' }) + ); }); }); diff --git a/src/reducer/notification.js b/src/reducer/notification.js index 6e348296..0ae0305e 100644 --- a/src/reducer/notification.js +++ b/src/reducer/notification.js @@ -1,4 +1,7 @@ -import { SHOW_NOTIFICATION, HIDE_NOTIFICATION } from '../actions/notificationActions'; +import { + SHOW_NOTIFICATION, + HIDE_NOTIFICATION, +} from '../actions/notificationActions'; const defaultState = { text: '', @@ -7,11 +10,11 @@ const defaultState = { export default (previousState = defaultState, { type, payload }) => { switch (type) { - case SHOW_NOTIFICATION: - return { text: payload.text, type: payload.type }; - case HIDE_NOTIFICATION: - return { ...previousState, text: '' }; - default: - return previousState; + case SHOW_NOTIFICATION: + return { text: payload.text, type: payload.type }; + case HIDE_NOTIFICATION: + return { ...previousState, text: '' }; + default: + return previousState; } }; diff --git a/src/reducer/notification.spec.js b/src/reducer/notification.spec.js index 4cb12077..a5e4c7cb 100644 --- a/src/reducer/notification.spec.js +++ b/src/reducer/notification.spec.js @@ -1,5 +1,8 @@ import assert from 'assert'; -import { SHOW_NOTIFICATION, HIDE_NOTIFICATION } from '../actions/notificationActions'; +import { + SHOW_NOTIFICATION, + HIDE_NOTIFICATION, +} from '../actions/notificationActions'; import reducer from './notification'; describe('notification reducer', () => { @@ -7,15 +10,21 @@ describe('notification reducer', () => { assert.deepEqual({ text: '', type: 'info' }, reducer(undefined, {})); }); it('should set text and type upon SHOW_NOTIFICATION', () => { - assert.deepEqual({ text: 'foo', type: 'warning' }, reducer(undefined, { - type: SHOW_NOTIFICATION, - payload: { text: 'foo', type: 'warning' }, - })); + assert.deepEqual( + { text: 'foo', type: 'warning' }, + reducer(undefined, { + type: SHOW_NOTIFICATION, + payload: { text: 'foo', type: 'warning' }, + }) + ); }); it('should set text to empty string upon HIDE_NOTIFICATION', () => { - assert.deepEqual({ text: '', type: 'warning' }, reducer( - { text: 'foo', type: 'warning' }, - { type: HIDE_NOTIFICATION } - )); + assert.deepEqual( + { text: '', type: 'warning' }, + reducer( + { text: 'foo', type: 'warning' }, + { type: HIDE_NOTIFICATION } + ) + ); }); }); diff --git a/src/reducer/references/oneToMany.js b/src/reducer/references/oneToMany.js index fb45ea2a..0ae708df 100644 --- a/src/reducer/references/oneToMany.js +++ b/src/reducer/references/oneToMany.js @@ -4,17 +4,18 @@ const initialState = {}; export default (previousState = initialState, { type, payload, meta }) => { switch (type) { - case CRUD_GET_MANY_REFERENCE_SUCCESS: - return { - ...previousState, - [meta.relatedTo]: payload.data.map(record => record.id), - }; - default: - return previousState; + case CRUD_GET_MANY_REFERENCE_SUCCESS: + return { + ...previousState, + [meta.relatedTo]: payload.data.map(record => record.id), + }; + default: + return previousState; } }; -export const getIds = (state, relatedTo) => state.admin.references.oneToMany[relatedTo]; +export const getIds = (state, relatedTo) => + state.admin.references.oneToMany[relatedTo]; export const getReferences = (state, reference, relatedTo) => { const ids = getIds(state, relatedTo); @@ -39,4 +40,5 @@ export const getReferencesByIds = (state, reference, ids) => { }, {}); }; -export const nameRelatedTo = (reference, id, resource, target) => `${resource}_${reference}@${target}_${id}`; +export const nameRelatedTo = (reference, id, resource, target) => + `${resource}_${reference}@${target}_${id}`; diff --git a/src/reducer/references/possibleValues.js b/src/reducer/references/possibleValues.js index 0232e7b5..529203a2 100644 --- a/src/reducer/references/possibleValues.js +++ b/src/reducer/references/possibleValues.js @@ -4,21 +4,33 @@ const initialState = {}; export default (previousState = initialState, { type, payload, meta }) => { switch (type) { - case CRUD_GET_MATCHING_SUCCESS: - return { - ...previousState, - [meta.relatedTo]: payload.data.map(record => record.id), - }; - default: - return previousState; + case CRUD_GET_MATCHING_SUCCESS: + return { + ...previousState, + [meta.relatedTo]: payload.data.map(record => record.id), + }; + default: + return previousState; } }; -export const getPossibleReferences = (state, referenceSource, reference, selectedIds = []) => { - const possibleValues = state.admin.references.possibleValues[referenceSource] +export const getPossibleReferences = ( + state, + referenceSource, + reference, + selectedIds = [] +) => { + const possibleValues = state.admin.references.possibleValues[ + referenceSource + ] ? Array.from(state.admin.references.possibleValues[referenceSource]) : []; - selectedIds.forEach(id => possibleValues.some(value => value == id) || possibleValues.unshift(id)); + selectedIds.forEach( + id => + possibleValues.some(value => value == id) || + possibleValues.unshift(id) + ); + return possibleValues .map(id => state.admin[reference].data[id]) .filter(r => typeof r !== 'undefined'); diff --git a/src/reducer/resource/data.js b/src/reducer/resource/data.js index d6c1a5cf..8e8a03f2 100644 --- a/src/reducer/resource/data.js +++ b/src/reducer/resource/data.js @@ -46,8 +46,9 @@ const addRecords = (newRecords = [], oldRecords) => { const latestValidDate = new Date(); latestValidDate.setTime(latestValidDate.getTime() - cacheDuration); const oldValidRecordIds = oldRecords.fetchedAt - ? Object.keys(oldRecords.fetchedAt) - .filter(id => oldRecords.fetchedAt[id] > latestValidDate) + ? Object.keys(oldRecords.fetchedAt).filter( + id => oldRecords.fetchedAt[id] > latestValidDate + ) : []; const oldValidRecords = oldValidRecordIds.reduce((prev, id) => { prev[id] = oldRecords[id]; // eslint-disable-line no-param-reassign @@ -62,17 +63,22 @@ const addRecords = (newRecords = [], oldRecords) => { ...oldValidRecords, ...newRecordsById, }; - Object.defineProperty(records, 'fetchedAt', { value: { - ...oldValidRecordsFetchedAt, - ...newRecordsFetchedAt, - } }); // non enumerable by default + Object.defineProperty(records, 'fetchedAt', { + value: { + ...oldValidRecordsFetchedAt, + ...newRecordsFetchedAt, + }, + }); // non enumerable by default return records; }; const initialState = {}; Object.defineProperty(initialState, 'fetchedAt', { value: {} }); // non enumerable by default -export default resource => (previousState = initialState, { payload, meta }) => { +export default resource => ( + previousState = initialState, + { payload, meta } +) => { if (!meta || meta.resource !== resource) { return previousState; } @@ -80,16 +86,16 @@ export default resource => (previousState = initialState, { payload, meta }) => return previousState; } switch (meta.fetchResponse) { - case GET_LIST: - case GET_MANY: - case GET_MANY_REFERENCE: - return addRecords(payload.data, previousState); - case GET_ONE: - case UPDATE: - case CREATE: - return addRecords([payload.data], previousState); - default: - return previousState; + case GET_LIST: + case GET_MANY: + case GET_MANY_REFERENCE: + return addRecords(payload.data, previousState); + case GET_ONE: + case UPDATE: + case CREATE: + return addRecords([payload.data], previousState); + default: + return previousState; } }; diff --git a/src/reducer/resource/index.js b/src/reducer/resource/index.js index 72d9afaf..4ea8eb78 100644 --- a/src/reducer/resource/index.js +++ b/src/reducer/resource/index.js @@ -2,7 +2,8 @@ import { combineReducers } from 'redux'; import data from './data'; import list from './list'; -export default (resource) => combineReducers({ - data: data(resource), - list: list(resource), -}); +export default resource => + combineReducers({ + data: data(resource), + list: list(resource), + }); diff --git a/src/reducer/resource/list/ids.js b/src/reducer/resource/list/ids.js index 170d4d52..bd974cce 100644 --- a/src/reducer/resource/list/ids.js +++ b/src/reducer/resource/list/ids.js @@ -1,22 +1,33 @@ -import { CRUD_GET_LIST_SUCCESS, CRUD_DELETE_SUCCESS } from '../../../actions/dataActions'; +import { + CRUD_GET_LIST_SUCCESS, + CRUD_DELETE_SUCCESS, +} from '../../../actions/dataActions'; -export default resource => (previousState = [], { type, payload, requestPayload, meta }) => { +export default resource => ( + previousState = [], + { type, payload, requestPayload, meta } +) => { if (!meta || meta.resource !== resource) { return previousState; } switch (type) { - case CRUD_GET_LIST_SUCCESS: - return payload.data.map(record => record.id); - case CRUD_DELETE_SUCCESS: { - const index = previousState.findIndex(el => el == requestPayload.id); // eslint-disable-line eqeqeq - if (index === -1) { - return previousState; + case CRUD_GET_LIST_SUCCESS: + return payload.data.map(record => record.id); + case CRUD_DELETE_SUCCESS: { + const index = previousState.findIndex( + el => el == requestPayload.id + ); // eslint-disable-line eqeqeq + if (index === -1) { + return previousState; + } + return [ + ...previousState.slice(0, index), + ...previousState.slice(index + 1), + ]; } - return [...previousState.slice(0, index), ...previousState.slice(index + 1)]; - } - default: - return previousState; + default: + return previousState; } }; -export const getIds = (state) => state; +export const getIds = state => state; diff --git a/src/reducer/resource/list/index.js b/src/reducer/resource/list/index.js index b1aabc45..33c5d387 100644 --- a/src/reducer/resource/list/index.js +++ b/src/reducer/resource/list/index.js @@ -3,8 +3,9 @@ import ids from './ids'; import params from './params'; import total from './total'; -export default resource => combineReducers({ - ids: ids(resource), - params: params(resource), - total: total(resource), -}); +export default resource => + combineReducers({ + ids: ids(resource), + params: params(resource), + total: total(resource), + }); diff --git a/src/reducer/resource/list/params.js b/src/reducer/resource/list/params.js index a268f4d7..295f122d 100644 --- a/src/reducer/resource/list/params.js +++ b/src/reducer/resource/list/params.js @@ -8,14 +8,17 @@ const defaultState = { filter: {}, }; -export default resource => (previousState = defaultState, { type, payload, meta }) => { +export default resource => ( + previousState = defaultState, + { type, payload, meta } +) => { if (!meta || meta.resource !== resource) { return previousState; } switch (type) { - case CRUD_CHANGE_LIST_PARAMS: - return payload; - default: - return previousState; + case CRUD_CHANGE_LIST_PARAMS: + return payload; + default: + return previousState; } }; diff --git a/src/reducer/resource/list/queryReducer.js b/src/reducer/resource/list/queryReducer.js index 5ae50dd5..8b55107e 100644 --- a/src/reducer/resource/list/queryReducer.js +++ b/src/reducer/resource/list/queryReducer.js @@ -6,37 +6,38 @@ export const SET_PAGE = 'SET_PAGE'; export const SET_FILTER = 'SET_FILTER'; -const oppositeOrder = direction => (direction === SORT_DESC ? SORT_ASC : SORT_DESC); +const oppositeOrder = direction => + direction === SORT_DESC ? SORT_ASC : SORT_DESC; /** * This reducer is for the react-router query string, NOT for redux. */ export default (previousState, { type, payload }) => { switch (type) { - case SET_SORT: - if (payload === previousState.sort) { + case SET_SORT: + if (payload === previousState.sort) { + return { + ...previousState, + order: oppositeOrder(previousState.order), + page: 1, + }; + } + return { ...previousState, - order: oppositeOrder(previousState.order), + sort: payload, + order: SORT_ASC, page: 1, }; - } - - return { - ...previousState, - sort: payload, - order: SORT_ASC, - page: 1, - }; - case SET_PAGE: - return { ...previousState, page: payload }; + case SET_PAGE: + return { ...previousState, page: payload }; - case SET_FILTER: { - return { ...previousState, page: 1, filter: payload }; - } + case SET_FILTER: { + return { ...previousState, page: 1, filter: payload }; + } - default: - return previousState; + default: + return previousState; } }; diff --git a/src/reducer/resource/list/queryReducer.spec.js b/src/reducer/resource/list/queryReducer.spec.js index ee14e7f2..dbd6e4f9 100644 --- a/src/reducer/resource/list/queryReducer.spec.js +++ b/src/reducer/resource/list/queryReducer.spec.js @@ -4,50 +4,65 @@ import queryReducer from './queryReducer'; describe('Query Reducer', () => { describe('SET_PAGE action', () => { it('should update the page', () => { - const updatedState = queryReducer({ - page: 1, - }, { - type: 'SET_PAGE', - payload: 2, - }); + const updatedState = queryReducer( + { + page: 1, + }, + { + type: 'SET_PAGE', + payload: 2, + } + ); assert.equal(updatedState.page, 2); }); it('should not update the filter', () => { const initialFilter = {}; - const updatedState = queryReducer({ - filter: initialFilter, - page: 1, - }, { - type: 'SET_PAGE', - payload: 2, - }); + const updatedState = queryReducer( + { + filter: initialFilter, + page: 1, + }, + { + type: 'SET_PAGE', + payload: 2, + } + ); assert.equal(updatedState.filter, initialFilter); }); }); describe('SET_FILTER action', () => { it('should add new filter with given value when set', () => { - const updatedState = queryReducer({}, { - type: 'SET_FILTER', - payload: { title: 'foo' }, - }); + const updatedState = queryReducer( + {}, + { + type: 'SET_FILTER', + payload: { title: 'foo' }, + } + ); assert.deepEqual(updatedState.filter, { title: 'foo' }); }); it('should replace existing filter with given value', () => { - const updatedState = queryReducer({ - filter: { - title: 'foo', + const updatedState = queryReducer( + { + filter: { + title: 'foo', + }, }, - }, { - type: 'SET_FILTER', - payload: { title: 'bar' }, - }); + { + type: 'SET_FILTER', + payload: { title: 'bar' }, + } + ); assert.deepEqual(updatedState.filter, { title: 'bar' }); }); it('should reset page to 1', () => { - const updatedState = queryReducer({ page: 3 }, { type: 'SET_FILTER', payload: {} }); + const updatedState = queryReducer( + { page: 3 }, + { type: 'SET_FILTER', payload: {} } + ); assert.equal(updatedState.page, 1); }); }); diff --git a/src/reducer/saving.js b/src/reducer/saving.js index a9519acf..af238247 100644 --- a/src/reducer/saving.js +++ b/src/reducer/saving.js @@ -1,23 +1,27 @@ import { actionTypes } from 'redux-form'; import { LOCATION_CHANGE } from 'react-router-redux'; import { - CRUD_CREATE, CRUD_CREATE_SUCCESS, CRUD_CREATE_FAILURE, - CRUD_UPDATE, CRUD_UPDATE_SUCCESS, CRUD_UPDATE_FAILURE, + CRUD_CREATE, + CRUD_CREATE_SUCCESS, + CRUD_CREATE_FAILURE, + CRUD_UPDATE, + CRUD_UPDATE_SUCCESS, + CRUD_UPDATE_FAILURE, } from '../actions'; export default (previousState = false, { type, payload }) => { switch (type) { - case CRUD_CREATE: - case CRUD_UPDATE: - return { redirect: payload.redirectTo }; - case LOCATION_CHANGE: - case actionTypes.SET_SUBMIT_FAILED: - case CRUD_CREATE_SUCCESS: - case CRUD_CREATE_FAILURE: - case CRUD_UPDATE_SUCCESS: - case CRUD_UPDATE_FAILURE: - return false; - default: - return previousState; + case CRUD_CREATE: + case CRUD_UPDATE: + return { redirect: payload.redirectTo }; + case LOCATION_CHANGE: + case actionTypes.SET_SUBMIT_FAILED: + case CRUD_CREATE_SUCCESS: + case CRUD_CREATE_FAILURE: + case CRUD_UPDATE_SUCCESS: + case CRUD_UPDATE_FAILURE: + return false; + default: + return previousState; } }; diff --git a/src/reducer/ui.js b/src/reducer/ui.js index 72553584..bce4644e 100644 --- a/src/reducer/ui.js +++ b/src/reducer/ui.js @@ -6,11 +6,14 @@ const defaultState = { export default (previousState = defaultState, { type, payload }) => { switch (type) { - case TOGGLE_SIDEBAR: - return { ...previousState, sidebarOpen: !previousState.sidebarOpen }; - case SET_SIDEBAR_VISIBILITY: - return { ...previousState, sidebarOpen: payload }; - default: - return previousState; + case TOGGLE_SIDEBAR: + return { + ...previousState, + sidebarOpen: !previousState.sidebarOpen, + }; + case SET_SIDEBAR_VISIBILITY: + return { ...previousState, sidebarOpen: payload }; + default: + return previousState; } }; diff --git a/src/reducer/ui.spec.js b/src/reducer/ui.spec.js index 96362489..e997cc12 100644 --- a/src/reducer/ui.spec.js +++ b/src/reducer/ui.spec.js @@ -7,13 +7,31 @@ describe('ui reducer', () => { assert.deepEqual({ sidebarOpen: false }, reducer(undefined, {})); }); it('should toggle sidebar visibility upon TOGGLE_SIDEBAR', () => { - assert.deepEqual({ sidebarOpen: false }, reducer({ sidebarOpen: true }, toggleSidebar())); - assert.deepEqual({ sidebarOpen: true }, reducer({ sidebarOpen: false }, toggleSidebar())); + assert.deepEqual( + { sidebarOpen: false }, + reducer({ sidebarOpen: true }, toggleSidebar()) + ); + assert.deepEqual( + { sidebarOpen: true }, + reducer({ sidebarOpen: false }, toggleSidebar()) + ); }); it('should set sidebar visibility upon SET_SIDEBAR_VISIBILITY', () => { - assert.deepEqual({ sidebarOpen: false }, reducer({ sidebarOpen: true }, setSidebarVisibility(false))); - assert.deepEqual({ sidebarOpen: true }, reducer({ sidebarOpen: true }, setSidebarVisibility(true))); - assert.deepEqual({ sidebarOpen: false }, reducer({ sidebarOpen: false }, setSidebarVisibility(false))); - assert.deepEqual({ sidebarOpen: true }, reducer({ sidebarOpen: false }, setSidebarVisibility(true))); + assert.deepEqual( + { sidebarOpen: false }, + reducer({ sidebarOpen: true }, setSidebarVisibility(false)) + ); + assert.deepEqual( + { sidebarOpen: true }, + reducer({ sidebarOpen: true }, setSidebarVisibility(true)) + ); + assert.deepEqual( + { sidebarOpen: false }, + reducer({ sidebarOpen: false }, setSidebarVisibility(false)) + ); + assert.deepEqual( + { sidebarOpen: true }, + reducer({ sidebarOpen: false }, setSidebarVisibility(true)) + ); }); }); diff --git a/src/rest/jsonServer.js b/src/rest/jsonServer.js index f99ed3ce..c4a8e336 100644 --- a/src/rest/jsonServer.js +++ b/src/rest/jsonServer.js @@ -33,52 +33,52 @@ export default (apiUrl, httpClient = fetchJson) => { let url = ''; const options = {}; switch (type) { - case GET_LIST: { - const { page, perPage } = params.pagination; - const { field, order } = params.sort; - const query = { - ...params.filter, - _sort: field, - _order: order, - _start: (page - 1) * perPage, - _end: page * perPage, - }; - url = `${apiUrl}/${resource}?${stringify(query)}`; - break; - } - case GET_ONE: - url = `${apiUrl}/${resource}/${params.id}`; - break; - case GET_MANY_REFERENCE: { - const { page, perPage } = params.pagination; - const { field, order } = params.sort; - const query = { - ...params.filter, - [params.target]: params.id, - _sort: field, - _order: order, - _start: (page - 1) * perPage, - _end: page * perPage, - }; - url = `${apiUrl}/${resource}?${stringify(query)}`; - break; - } - case UPDATE: - url = `${apiUrl}/${resource}/${params.id}`; - options.method = 'PUT'; - options.body = JSON.stringify(params.data); - break; - case CREATE: - url = `${apiUrl}/${resource}`; - options.method = 'POST'; - options.body = JSON.stringify(params.data); - break; - case DELETE: - url = `${apiUrl}/${resource}/${params.id}`; - options.method = 'DELETE'; - break; - default: - throw new Error(`Unsupported fetch action type ${type}`); + case GET_LIST: { + const { page, perPage } = params.pagination; + const { field, order } = params.sort; + const query = { + ...params.filter, + _sort: field, + _order: order, + _start: (page - 1) * perPage, + _end: page * perPage, + }; + url = `${apiUrl}/${resource}?${stringify(query)}`; + break; + } + case GET_ONE: + url = `${apiUrl}/${resource}/${params.id}`; + break; + case GET_MANY_REFERENCE: { + const { page, perPage } = params.pagination; + const { field, order } = params.sort; + const query = { + ...params.filter, + [params.target]: params.id, + _sort: field, + _order: order, + _start: (page - 1) * perPage, + _end: page * perPage, + }; + url = `${apiUrl}/${resource}?${stringify(query)}`; + break; + } + case UPDATE: + url = `${apiUrl}/${resource}/${params.id}`; + options.method = 'PUT'; + options.body = JSON.stringify(params.data); + break; + case CREATE: + url = `${apiUrl}/${resource}`; + options.method = 'POST'; + options.body = JSON.stringify(params.data); + break; + case DELETE: + url = `${apiUrl}/${resource}/${params.id}`; + options.method = 'DELETE'; + break; + default: + throw new Error(`Unsupported fetch action type ${type}`); } return { url, options }; }; @@ -93,19 +93,24 @@ export default (apiUrl, httpClient = fetchJson) => { const convertHTTPResponseToREST = (response, type, resource, params) => { const { headers, json } = response; switch (type) { - case GET_LIST: - case GET_MANY_REFERENCE: - if (!headers.has('x-total-count')) { - throw new Error('The X-Total-Count header is missing in the HTTP Response. The jsonServer REST client expects responses for lists of resources to contain this header with the total number of results to build the pagination. If you are using CORS, did you declare X-Total-Count in the Access-Control-Expose-Headers header?'); - } - return { - data: json, - total: parseInt(headers.get('x-total-count').split('/').pop(), 10), - }; - case CREATE: - return { data: { ...params.data, id: json.id } }; - default: - return { data: json }; + case GET_LIST: + case GET_MANY_REFERENCE: + if (!headers.has('x-total-count')) { + throw new Error( + 'The X-Total-Count header is missing in the HTTP Response. The jsonServer REST client expects responses for lists of resources to contain this header with the total number of results to build the pagination. If you are using CORS, did you declare X-Total-Count in the Access-Control-Expose-Headers header?' + ); + } + return { + data: json, + total: parseInt( + headers.get('x-total-count').split('/').pop(), + 10 + ), + }; + case CREATE: + return { data: { ...params.data, id: json.id } }; + default: + return { data: json }; } }; @@ -118,11 +123,19 @@ export default (apiUrl, httpClient = fetchJson) => { return (type, resource, params) => { // json-server doesn't handle WHERE IN requests, so we fallback to calling GET_ONE n times instead if (type === GET_MANY) { - return Promise.all(params.ids.map(id => httpClient(`${apiUrl}/${resource}/${id}`))) - .then(responses => ({ data: responses.map(response => response.json) })); + return Promise.all( + params.ids.map(id => httpClient(`${apiUrl}/${resource}/${id}`)) + ).then(responses => ({ + data: responses.map(response => response.json), + })); } - const { url, options } = convertRESTRequestToHTTP(type, resource, params); - return httpClient(url, options) - .then(response => convertHTTPResponseToREST(response, type, resource, params)); + const { url, options } = convertRESTRequestToHTTP( + type, + resource, + params + ); + return httpClient(url, options).then(response => + convertHTTPResponseToREST(response, type, resource, params) + ); }; }; diff --git a/src/rest/simple.js b/src/rest/simple.js index c9bdc729..a2e9675b 100644 --- a/src/rest/simple.js +++ b/src/rest/simple.js @@ -34,54 +34,63 @@ export default (apiUrl, httpClient = fetchJson) => { let url = ''; const options = {}; switch (type) { - case GET_LIST: { - const { page, perPage } = params.pagination; - const { field, order } = params.sort; - const query = { - sort: JSON.stringify([field, order]), - range: JSON.stringify([(page - 1) * perPage, (page * perPage) - 1]), - filter: JSON.stringify(params.filter), - }; - url = `${apiUrl}/${resource}?${stringify(query)}`; - break; - } - case GET_ONE: - url = `${apiUrl}/${resource}/${params.id}`; - break; - case GET_MANY: { - const query = { - filter: JSON.stringify({ id: params.ids }), - }; - url = `${apiUrl}/${resource}?${stringify(query)}`; - break; - } - case GET_MANY_REFERENCE: { - const { page, perPage } = params.pagination; - const { field, order } = params.sort; - const query = { - sort: JSON.stringify([field, order]), - range: JSON.stringify([(page - 1) * perPage, (page * perPage) - 1]), - filter: JSON.stringify({ ...params.filter, [params.target]: params.id }), - }; - url = `${apiUrl}/${resource}?${stringify(query)}`; - break; - } - case UPDATE: - url = `${apiUrl}/${resource}/${params.id}`; - options.method = 'PUT'; - options.body = JSON.stringify(params.data); - break; - case CREATE: - url = `${apiUrl}/${resource}`; - options.method = 'POST'; - options.body = JSON.stringify(params.data); - break; - case DELETE: - url = `${apiUrl}/${resource}/${params.id}`; - options.method = 'DELETE'; - break; - default: - throw new Error(`Unsupported fetch action type ${type}`); + case GET_LIST: { + const { page, perPage } = params.pagination; + const { field, order } = params.sort; + const query = { + sort: JSON.stringify([field, order]), + range: JSON.stringify([ + (page - 1) * perPage, + page * perPage - 1, + ]), + filter: JSON.stringify(params.filter), + }; + url = `${apiUrl}/${resource}?${stringify(query)}`; + break; + } + case GET_ONE: + url = `${apiUrl}/${resource}/${params.id}`; + break; + case GET_MANY: { + const query = { + filter: JSON.stringify({ id: params.ids }), + }; + url = `${apiUrl}/${resource}?${stringify(query)}`; + break; + } + case GET_MANY_REFERENCE: { + const { page, perPage } = params.pagination; + const { field, order } = params.sort; + const query = { + sort: JSON.stringify([field, order]), + range: JSON.stringify([ + (page - 1) * perPage, + page * perPage - 1, + ]), + filter: JSON.stringify({ + ...params.filter, + [params.target]: params.id, + }), + }; + url = `${apiUrl}/${resource}?${stringify(query)}`; + break; + } + case UPDATE: + url = `${apiUrl}/${resource}/${params.id}`; + options.method = 'PUT'; + options.body = JSON.stringify(params.data); + break; + case CREATE: + url = `${apiUrl}/${resource}`; + options.method = 'POST'; + options.body = JSON.stringify(params.data); + break; + case DELETE: + url = `${apiUrl}/${resource}/${params.id}`; + options.method = 'DELETE'; + break; + default: + throw new Error(`Unsupported fetch action type ${type}`); } return { url, options }; }; @@ -96,19 +105,24 @@ export default (apiUrl, httpClient = fetchJson) => { const convertHTTPResponseToREST = (response, type, resource, params) => { const { headers, json } = response; switch (type) { - case GET_LIST: - case GET_MANY_REFERENCE: - if (!headers.has('content-range')) { - throw new Error('The Content-Range header is missing in the HTTP Response. The simple REST client expects responses for lists of resources to contain this header with the total number of results to build the pagination. If you are using CORS, did you declare Content-Range in the Access-Control-Expose-Headers header?'); - } - return { - data: json, - total: parseInt(headers.get('content-range').split('/').pop(), 10), - }; - case CREATE: - return { data: { ...params.data, id: json.id } }; - default: - return { data: json }; + case GET_LIST: + case GET_MANY_REFERENCE: + if (!headers.has('content-range')) { + throw new Error( + 'The Content-Range header is missing in the HTTP Response. The simple REST client expects responses for lists of resources to contain this header with the total number of results to build the pagination. If you are using CORS, did you declare Content-Range in the Access-Control-Expose-Headers header?' + ); + } + return { + data: json, + total: parseInt( + headers.get('content-range').split('/').pop(), + 10 + ), + }; + case CREATE: + return { data: { ...params.data, id: json.id } }; + default: + return { data: json }; } }; @@ -119,8 +133,13 @@ export default (apiUrl, httpClient = fetchJson) => { * @returns {Promise} the Promise for a REST response */ return (type, resource, params) => { - const { url, options } = convertRESTRequestToHTTP(type, resource, params); - return httpClient(url, options) - .then(response => convertHTTPResponseToREST(response, type, resource, params)); + const { url, options } = convertRESTRequestToHTTP( + type, + resource, + params + ); + return httpClient(url, options).then(response => + convertHTTPResponseToREST(response, type, resource, params) + ); }; }; diff --git a/src/sideEffect/saga/auth.js b/src/sideEffect/saga/auth.js index 24ab797d..4bb37e98 100644 --- a/src/sideEffect/saga/auth.js +++ b/src/sideEffect/saga/auth.js @@ -1,7 +1,10 @@ import { all, put, call, takeEvery } from 'redux-saga/effects'; import { push, replace } from 'react-router-redux'; -import { showNotification, hideNotification } from '../../actions/notificationActions'; +import { + showNotification, + hideNotification, +} from '../../actions/notificationActions'; import { USER_LOGIN, USER_LOGIN_LOADING, @@ -13,52 +16,68 @@ import { import { FETCH_ERROR } from '../../actions/fetchActions'; import { AUTH_LOGIN, AUTH_CHECK, AUTH_ERROR, AUTH_LOGOUT } from '../../auth'; -export default (authClient) => { +export default authClient => { if (!authClient) return () => null; function* handleAuth(action) { const { type, payload, error, meta } = action; switch (type) { - case USER_LOGIN: { - try { - yield put({ type: USER_LOGIN_LOADING }); - const authPayload = yield call(authClient, AUTH_LOGIN, payload); - yield put({ type: USER_LOGIN_SUCCESS, payload: authPayload }); - yield put(push(meta.pathName || '/')); - } catch (e) { - yield put({ type: USER_LOGIN_FAILURE, error: e, meta: { auth: true } }); - const errorMessage = typeof e === 'string' - ? e - : (typeof e === 'undefined' || !e.message ? 'aor.auth.sign_in_error' : e.message); - yield put(showNotification(errorMessage, 'warning')); + case USER_LOGIN: { + try { + yield put({ type: USER_LOGIN_LOADING }); + const authPayload = yield call( + authClient, + AUTH_LOGIN, + payload + ); + yield put({ + type: USER_LOGIN_SUCCESS, + payload: authPayload, + }); + yield put(push(meta.pathName || '/')); + } catch (e) { + yield put({ + type: USER_LOGIN_FAILURE, + error: e, + meta: { auth: true }, + }); + const errorMessage = + typeof e === 'string' + ? e + : typeof e === 'undefined' || !e.message + ? 'aor.auth.sign_in_error' + : e.message; + yield put(showNotification(errorMessage, 'warning')); + } + break; } - break; - } - case USER_CHECK: { - try { - yield call(authClient, AUTH_CHECK, payload); - } catch (e) { - yield call(authClient, AUTH_LOGOUT); - yield put(replace({ - pathname: (e && e.redirectTo) || '/login', - state: { nextPathname: meta.pathName }, - })); + case USER_CHECK: { + try { + yield call(authClient, AUTH_CHECK, payload); + } catch (e) { + yield call(authClient, AUTH_LOGOUT); + yield put( + replace({ + pathname: (e && e.redirectTo) || '/login', + state: { nextPathname: meta.pathName }, + }) + ); + } + break; } - break; - } - case USER_LOGOUT: { - yield call(authClient, AUTH_LOGOUT); - yield put(push('/login')); - break; - } - case FETCH_ERROR: - try { - yield call(authClient, AUTH_ERROR, error); - } catch (e) { + case USER_LOGOUT: { yield call(authClient, AUTH_LOGOUT); yield put(push('/login')); - yield put(hideNotification()); + break; } - break; + case FETCH_ERROR: + try { + yield call(authClient, AUTH_ERROR, error); + } catch (e) { + yield call(authClient, AUTH_LOGOUT); + yield put(push('/login')); + yield put(hideNotification()); + } + break; } } return function* watchAuthActions() { diff --git a/src/sideEffect/saga/crudFetch.js b/src/sideEffect/saga/crudFetch.js index c2fdf8e8..fc1937ae 100644 --- a/src/sideEffect/saga/crudFetch.js +++ b/src/sideEffect/saga/crudFetch.js @@ -1,4 +1,11 @@ -import { all, put, call, cancelled, takeEvery, takeLatest } from 'redux-saga/effects'; +import { + all, + put, + call, + cancelled, + takeEvery, + takeLatest, +} from 'redux-saga/effects'; import { FETCH_START, FETCH_END, @@ -6,7 +13,7 @@ import { FETCH_CANCEL, } from '../../actions/fetchActions'; -const crudFetch = (restClient) => { +const crudFetch = restClient => { function* handleFetch(action) { const { type, payload, meta: { fetch: fetchMeta, ...meta } } = action; const restType = fetchMeta; @@ -25,7 +32,11 @@ const crudFetch = (restClient) => { type: `${type}_SUCCESS`, payload: response, requestPayload: payload, - meta: { ...meta, fetchResponse: restType, fetchStatus: FETCH_END }, + meta: { + ...meta, + fetchResponse: restType, + fetchStatus: FETCH_END, + }, }); yield put({ type: FETCH_END }); } catch (error) { @@ -33,7 +44,11 @@ const crudFetch = (restClient) => { type: `${type}_FAILURE`, error: error.message ? error.message : error, requestPayload: payload, - meta: { ...meta, fetchResponse: restType, fetchStatus: FETCH_ERROR }, + meta: { + ...meta, + fetchResponse: restType, + fetchStatus: FETCH_ERROR, + }, }); yield put({ type: FETCH_ERROR, error }); } finally { @@ -46,11 +61,22 @@ const crudFetch = (restClient) => { return function* watchCrudFetch() { yield all([ - takeLatest(action => action.meta && action.meta.fetch && action.meta.cancelPrevious, handleFetch), - takeEvery(action => action.meta && action.meta.fetch && !action.meta.cancelPrevious, handleFetch), + takeLatest( + action => + action.meta && + action.meta.fetch && + action.meta.cancelPrevious, + handleFetch + ), + takeEvery( + action => + action.meta && + action.meta.fetch && + !action.meta.cancelPrevious, + handleFetch + ), ]); }; }; - export default crudFetch; diff --git a/src/sideEffect/saga/crudResponse.js b/src/sideEffect/saga/crudResponse.js index d4b6f449..bb339953 100644 --- a/src/sideEffect/saga/crudResponse.js +++ b/src/sideEffect/saga/crudResponse.js @@ -22,43 +22,84 @@ import resolveRedirectTo from '../../util/resolveRedirectTo'; */ function* handleResponse({ type, requestPayload, error, payload }) { switch (type) { - case CRUD_UPDATE_SUCCESS: - return requestPayload.redirectTo ? yield all([ - put(showNotification('aor.notification.updated')), - put(push(resolveRedirectTo(requestPayload.redirectTo, requestPayload.basePath, requestPayload.id))), - ]) : yield [put(showNotification('aor.notification.updated'))]; - case CRUD_CREATE_SUCCESS: - return requestPayload.redirectTo ? yield all([ - put(showNotification('aor.notification.created')), - put(push(resolveRedirectTo(requestPayload.redirectTo, requestPayload.basePath, payload.data.id))), - ]) : yield [put(showNotification('aor.notification.created'))]; - case CRUD_DELETE_SUCCESS: - return requestPayload.redirectTo ? yield all([ - put(showNotification('aor.notification.deleted')), - put(push(resolveRedirectTo(requestPayload.redirectTo, requestPayload.basePath, requestPayload.id))), - ]) : yield [put(showNotification('aor.notification.deleted'))]; - case CRUD_GET_ONE_FAILURE: - return requestPayload.basePath ? yield all([ - put(showNotification('aor.notification.item_doesnt_exist', 'warning')), - put(push(requestPayload.basePath)), - ]) : yield all([]); - case CRUD_GET_LIST_FAILURE: - case CRUD_GET_MANY_FAILURE: - case CRUD_GET_MANY_REFERENCE_FAILURE: - case CRUD_CREATE_FAILURE: - case CRUD_UPDATE_FAILURE: - case CRUD_DELETE_FAILURE: { - console.error(error); - const errorMessage = typeof error === 'string' - ? error - : (error.message || 'aor.notification.http_error'); - return yield put(showNotification(errorMessage, 'warning')); - } - default: - return yield all([]); + case CRUD_UPDATE_SUCCESS: + return requestPayload.redirectTo + ? yield all([ + put(showNotification('aor.notification.updated')), + put( + push( + resolveRedirectTo( + requestPayload.redirectTo, + requestPayload.basePath, + requestPayload.id + ) + ) + ), + ]) + : yield [put(showNotification('aor.notification.updated'))]; + case CRUD_CREATE_SUCCESS: + return requestPayload.redirectTo + ? yield all([ + put(showNotification('aor.notification.created')), + put( + push( + resolveRedirectTo( + requestPayload.redirectTo, + requestPayload.basePath, + payload.data.id + ) + ) + ), + ]) + : yield [put(showNotification('aor.notification.created'))]; + case CRUD_DELETE_SUCCESS: + return requestPayload.redirectTo + ? yield all([ + put(showNotification('aor.notification.deleted')), + put( + push( + resolveRedirectTo( + requestPayload.redirectTo, + requestPayload.basePath, + requestPayload.id + ) + ) + ), + ]) + : yield [put(showNotification('aor.notification.deleted'))]; + case CRUD_GET_ONE_FAILURE: + return requestPayload.basePath + ? yield all([ + put( + showNotification( + 'aor.notification.item_doesnt_exist', + 'warning' + ) + ), + put(push(requestPayload.basePath)), + ]) + : yield all([]); + case CRUD_GET_LIST_FAILURE: + case CRUD_GET_MANY_FAILURE: + case CRUD_GET_MANY_REFERENCE_FAILURE: + case CRUD_CREATE_FAILURE: + case CRUD_UPDATE_FAILURE: + case CRUD_DELETE_FAILURE: { + console.error(error); + const errorMessage = + typeof error === 'string' + ? error + : error.message || 'aor.notification.http_error'; + return yield put(showNotification(errorMessage, 'warning')); + } + default: + return yield all([]); } } -export default function* () { - yield takeEvery(action => action.meta && action.meta.fetchResponse, handleResponse); +export default function*() { + yield takeEvery( + action => action.meta && action.meta.fetchResponse, + handleResponse + ); } diff --git a/src/sideEffect/saga/crudSaga.js b/src/sideEffect/saga/crudSaga.js index 32de1377..53476f9c 100644 --- a/src/sideEffect/saga/crudSaga.js +++ b/src/sideEffect/saga/crudSaga.js @@ -7,11 +7,12 @@ import referenceFetch from './referenceFetch'; /** * @param {Object} restClient A REST object with two methods: fetch() and convertResponse() */ -export default (restClient, authClient) => function* crudSaga() { - yield all([ - auth(authClient)(), - crudFetch(restClient)(), - crudResponse(), - referenceFetch(), - ]); -}; +export default (restClient, authClient) => + function* crudSaga() { + yield all([ + auth(authClient)(), + crudFetch(restClient)(), + crudResponse(), + referenceFetch(), + ]); + }; diff --git a/src/sideEffect/saga/referenceFetch.js b/src/sideEffect/saga/referenceFetch.js index 50c2969a..f9405584 100644 --- a/src/sideEffect/saga/referenceFetch.js +++ b/src/sideEffect/saga/referenceFetch.js @@ -14,11 +14,11 @@ const addIds = (resource, ids) => { if (!debouncedIds[resource]) { debouncedIds[resource] = {}; } - ids.forEach((id) => { + ids.forEach(id => { debouncedIds[resource][id] = true; }); // fast UNIQUE }; -const getIds = (resource) => { +const getIds = resource => { const ids = Object.keys(debouncedIds[resource]); delete debouncedIds[resource]; return ids; @@ -57,6 +57,9 @@ function* accumulate({ payload, meta }) { tasks[resource] = yield fork(finalize, resource, meta.accumulate); } -export default function* () { - yield takeEvery(action => action.meta && action.meta.accumulate, accumulate); +export default function*() { + yield takeEvery( + action => action.meta && action.meta.accumulate, + accumulate + ); } diff --git a/src/util/FieldTitle.js b/src/util/FieldTitle.js index 8058d2df..db633ecb 100644 --- a/src/util/FieldTitle.js +++ b/src/util/FieldTitle.js @@ -6,20 +6,23 @@ import compose from 'recompose/compose'; import translate from '../i18n/translate'; -export const FieldTitle = ({ resource, source, label, isRequired, translate }) => ( +export const FieldTitle = ({ + resource, + source, + label, + isRequired, + translate, +}) => <span> - {typeof label !== 'undefined' ? - translate(label, { _: label }) - : - (typeof source !== 'undefined' ? - translate(`resources.${resource}.fields.${source}`, { _: inflection.humanize(source) }) - : - '' - ) - } + {typeof label !== 'undefined' + ? translate(label, { _: label }) + : typeof source !== 'undefined' + ? translate(`resources.${resource}.fields.${source}`, { + _: inflection.humanize(source), + }) + : ''} {isRequired && ' *'} - </span> -); + </span>; FieldTitle.propTypes = { isRequired: PropTypes.bool, diff --git a/src/util/FieldTitle.spec.js b/src/util/FieldTitle.spec.js index 0ee51798..807e6dc4 100644 --- a/src/util/FieldTitle.spec.js +++ b/src/util/FieldTitle.spec.js @@ -5,33 +5,59 @@ import React from 'react'; import { FieldTitle } from './FieldTitle'; describe('FieldTitle', () => { - const translateMock = dictionary => (term, options) => dictionary[term] || options._ || ''; - it('should return empty span by default', () => assert.equal( - shallow(<FieldTitle />).html(), - '<span></span>', - )); - it('should use the label when given', () => assert.equal( - shallow(<FieldTitle label="foo" />).html(), - '<span>foo</span>', - )); - it('should the label as translate key when translation is available', () => assert.equal( - shallow(<FieldTitle label="foo" translate={translateMock({ foo: 'bar' })} />).html(), - '<span>bar</span>', - )); - it('should use the humanized source when given', () => assert.equal( - shallow(<FieldTitle resource="posts" source="title" translate={translateMock({})} />).html(), - '<span>Title</span>', - )); - it('should use the source and resource as translate key when translation is available', () => assert.equal( - shallow(<FieldTitle resource="posts" source="title" translate={translateMock({ 'resources.posts.fields.title': 'titre' })} />).html(), - '<span>titre</span>', - )); - it('should use label rather than source', () => assert.equal( - shallow(<FieldTitle label="foo" resource="posts" source="title" />).html(), - '<span>foo</span>', - )); - it('should add a trailing asterisk if the field is required', () => assert.equal( - shallow(<FieldTitle label="foo" isRequired />).html(), - '<span>foo *</span>', - )); + const translateMock = dictionary => (term, options) => + dictionary[term] || options._ || ''; + it('should return empty span by default', () => + assert.equal(shallow(<FieldTitle />).html(), '<span></span>')); + it('should use the label when given', () => + assert.equal( + shallow(<FieldTitle label="foo" />).html(), + '<span>foo</span>' + )); + it('should the label as translate key when translation is available', () => + assert.equal( + shallow( + <FieldTitle + label="foo" + translate={translateMock({ foo: 'bar' })} + /> + ).html(), + '<span>bar</span>' + )); + it('should use the humanized source when given', () => + assert.equal( + shallow( + <FieldTitle + resource="posts" + source="title" + translate={translateMock({})} + /> + ).html(), + '<span>Title</span>' + )); + it('should use the source and resource as translate key when translation is available', () => + assert.equal( + shallow( + <FieldTitle + resource="posts" + source="title" + translate={translateMock({ + 'resources.posts.fields.title': 'titre', + })} + /> + ).html(), + '<span>titre</span>' + )); + it('should use label rather than source', () => + assert.equal( + shallow( + <FieldTitle label="foo" resource="posts" source="title" /> + ).html(), + '<span>foo</span>' + )); + it('should add a trailing asterisk if the field is required', () => + assert.equal( + shallow(<FieldTitle label="foo" isRequired />).html(), + '<span>foo *</span>' + )); }); diff --git a/src/util/HttpError.js b/src/util/HttpError.js index 7c6c9276..3910b780 100644 --- a/src/util/HttpError.js +++ b/src/util/HttpError.js @@ -7,7 +7,7 @@ class HttpError extends Error { if (typeof Error.captureStackTrace === 'function') { Error.captureStackTrace(this, this.constructor); } else { - this.stack = (new Error(message)).stack; + this.stack = new Error(message).stack; } this.stack = new Error().stack; } diff --git a/src/util/fetch.js b/src/util/fetch.js index e36051cc..2ede3076 100644 --- a/src/util/fetch.js +++ b/src/util/fetch.js @@ -2,10 +2,15 @@ import HttpError from './HttpError'; import { stringify } from 'query-string'; export const fetchJson = (url, options = {}) => { - const requestHeaders = options.headers || new Headers({ - Accept: 'application/json', - }); - if (!requestHeaders.has('Content-Type') && !(options && options.body && options.body instanceof FormData)) { + const requestHeaders = + options.headers || + new Headers({ + Accept: 'application/json', + }); + if ( + !requestHeaders.has('Content-Type') && + !(options && options.body && options.body instanceof FormData) + ) { requestHeaders.set('Content-Type', 'application/json'); } if (options.user && options.user.authenticated && options.user.token) { @@ -13,12 +18,14 @@ export const fetchJson = (url, options = {}) => { } return fetch(url, { ...options, headers: requestHeaders }) - .then(response => response.text().then(text => ({ - status: response.status, - statusText: response.statusText, - headers: response.headers, - body: text, - }))) + .then(response => + response.text().then(text => ({ + status: response.status, + statusText: response.statusText, + headers: response.headers, + body: text, + })) + ) .then(({ status, statusText, headers, body }) => { let json; try { @@ -27,7 +34,9 @@ export const fetchJson = (url, options = {}) => { // not json, no big deal } if (status < 200 || status >= 300) { - return Promise.reject(new HttpError((json && json.message) || statusText, status)); + return Promise.reject( + new HttpError((json && json.message) || statusText, status) + ); } return { status, headers, body, json }; }); diff --git a/src/util/linkToRecord.js b/src/util/linkToRecord.js index cd5d9007..13abfff0 100644 --- a/src/util/linkToRecord.js +++ b/src/util/linkToRecord.js @@ -1,2 +1 @@ -export default (basePath, id) => - `${basePath}/${encodeURIComponent(id)}` +export default (basePath, id) => `${basePath}/${encodeURIComponent(id)}`; diff --git a/src/util/linkToRecord.spec.js b/src/util/linkToRecord.spec.js index 6fc04469..5d6e5389 100644 --- a/src/util/linkToRecord.spec.js +++ b/src/util/linkToRecord.spec.js @@ -2,9 +2,12 @@ import assert from 'assert'; import linkToRecord from './linkToRecord'; describe('Linking to a record', () => { - it('should generate valid links', () => { - assert.equal(linkToRecord('books', 22), 'books/22'); - assert.equal(linkToRecord('books', '/books/13'), 'books/%2Fbooks%2F13'); - assert.equal(linkToRecord('blogs', 'https://dunglas.fr'), 'blogs/https%3A%2F%2Fdunglas.fr'); - }); + it('should generate valid links', () => { + assert.equal(linkToRecord('books', 22), 'books/22'); + assert.equal(linkToRecord('books', '/books/13'), 'books/%2Fbooks%2F13'); + assert.equal( + linkToRecord('blogs', 'https://dunglas.fr'), + 'blogs/https%3A%2F%2Fdunglas.fr' + ); + }); }); diff --git a/src/util/removeKey.js b/src/util/removeKey.js index 24997edd..bcc5c0f1 100644 --- a/src/util/removeKey.js +++ b/src/util/removeKey.js @@ -1,6 +1,5 @@ -const removeKey = (target, path) => Object - .keys(target) - .reduce((acc, key) => { +const removeKey = (target, path) => + Object.keys(target).reduce((acc, key) => { if (key !== path) { return Object.assign({}, acc, { [key]: target[key] }); } diff --git a/src/util/resolveRedirectTo.js b/src/util/resolveRedirectTo.js index ad6a636c..7000b6d8 100644 --- a/src/util/resolveRedirectTo.js +++ b/src/util/resolveRedirectTo.js @@ -2,15 +2,15 @@ import linkToRecord from './linkToRecord'; export default (redirectTo, basePath, id) => { switch (redirectTo) { - case 'list': - return basePath; - case 'create': - return `${basePath}/create`; - case 'edit': - return linkToRecord(basePath, id); - case 'show': - return `${linkToRecord(basePath, id)}/show`; - default: - return redirectTo; + case 'list': + return basePath; + case 'create': + return `${basePath}/create`; + case 'edit': + return linkToRecord(basePath, id); + case 'show': + return `${linkToRecord(basePath, id)}/show`; + default: + return redirectTo; } }; From 0b35a89403a2f2a58572da2dc7502ed4a74d2d2c Mon Sep 17 00:00:00 2001 From: Francois Zaninotto <fzaninotto@gmail.com> Date: Mon, 3 Jul 2017 15:33:39 +0200 Subject: [PATCH 04/65] Add linting to CI --- .eslintignore | 7 + Makefile | 5 +- docs/_layouts/default.html | 1040 ++++++++++++------------ e2e/chromeDriver.js | 4 +- e2e/pages/CreatePage.js | 29 +- e2e/pages/DeletePage.js | 17 +- e2e/pages/EditPage.js | 16 +- e2e/pages/ListPage.js | 33 +- e2e/pages/LoginPage.js | 2 +- e2e/pages/ShowPage.js | 16 +- e2e/tests/auth.js | 18 +- e2e/tests/create.js | 25 +- e2e/tests/edit.js | 19 +- e2e/tests/list.js | 37 +- e2e/tests/server.js | 16 +- e2e/tests/show.js | 9 +- src/CrudRoute.js | 24 +- src/mui/field/TextField.js | 8 +- src/mui/form/Toolbar.js | 69 +- src/mui/input/AutocompleteInput.js | 4 +- src/mui/input/DateInput.js | 52 +- src/mui/input/FileInput.js | 37 +- src/mui/input/FileInputPreview.spec.js | 22 +- src/mui/input/ImageInput.spec.js | 72 +- src/mui/input/SelectArrayInput.js | 4 +- src/mui/input/TextInput.js | 4 +- src/util/fetch.spec.js | 9 +- 27 files changed, 906 insertions(+), 692 deletions(-) create mode 100644 .eslintignore diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 00000000..815428b3 --- /dev/null +++ b/.eslintignore @@ -0,0 +1,7 @@ +node_modules +lib +example/static +docs/_site/ +docs/js/ +docs/css/ + diff --git a/Makefile b/Makefile index b22f1ae4..61085c65 100644 --- a/Makefile +++ b/Makefile @@ -21,7 +21,10 @@ watch: ## continuously compile ES6 files to JS doc: ## compile doc as html and launch doc web server @cd docs && jekyll server . --watch -test: test-unit test-e2e ## launch all tests +lint: ## lint the code and check coding conventions + @./node_modules/.bin/eslint . + +test: lint test-unit test-e2e ## launch all tests test-unit: ## launch unit tests @NODE_ENV=test NODE_ICU_DATA=node_modules/full-icu ./node_modules/.bin/mocha \ diff --git a/docs/_layouts/default.html b/docs/_layouts/default.html index 62607631..1890775a 100644 --- a/docs/_layouts/default.html +++ b/docs/_layouts/default.html @@ -1,527 +1,528 @@ <!DOCTYPE HTML> <html lang="en-US"> - <head> - <title>Admin-on-rest - {{ page.title }} - - - - - - - - - - - - - - - -
-
- +
+
+
+
+
-
-
+
+
- {{ content }} + {{ content }}
@@ -531,14 +532,17 @@
- + + diff --git a/e2e/chromeDriver.js b/e2e/chromeDriver.js index 170f92f6..8114ba38 100644 --- a/e2e/chromeDriver.js +++ b/e2e/chromeDriver.js @@ -1,6 +1,4 @@ import 'chromedriver'; import webdriver from 'selenium-webdriver'; -module.exports = new webdriver.Builder() - .forBrowser('chrome') - .build(); +module.exports = new webdriver.Builder().forBrowser('chrome').build(); diff --git a/e2e/pages/CreatePage.js b/e2e/pages/CreatePage.js index e0da464d..c8508158 100644 --- a/e2e/pages/CreatePage.js +++ b/e2e/pages/CreatePage.js @@ -3,9 +3,12 @@ import { By, until } from 'selenium-webdriver'; module.exports = url => driver => ({ elements: { appLoader: By.css('.app-loader'), - input: (name, type = 'input') => By.css(`.create-page ${type}[name='${name}']`), + input: (name, type = 'input') => + By.css(`.create-page ${type}[name='${name}']`), submitButton: By.css(".create-page button[type='submit']"), - submitAndAddButton: By.css(".create-page form>div:last-child button[type='button']"), + submitAndAddButton: By.css( + ".create-page form>div:last-child button[type='button']" + ), descInput: By.css('.ql-editor'), }, @@ -20,9 +23,19 @@ module.exports = url => driver => ({ waitUntilDataLoaded() { let continued = true; - return driver.wait(until.elementLocated(this.elements.appLoader), 2000) - .catch(() => continued = false) // no loader - we're on the same page ! - .then(() => continued ? driver.wait(until.stalenessOf(driver.findElement(this.elements.appLoader))) : true) + return driver + .wait(until.elementLocated(this.elements.appLoader), 2000) + .catch(() => (continued = false)) // no loader - we're on the same page ! + .then( + () => + continued + ? driver.wait( + until.stalenessOf( + driver.findElement(this.elements.appLoader) + ) + ) + : true + ) .catch(() => {}) // The element might have disapeared before the wait on the previous line .then(() => driver.sleep(100)); // let some time to redraw }, @@ -38,7 +51,7 @@ module.exports = url => driver => ({ setValues(values, clearPreviousValue = true) { let input; let lastPromise; - values.forEach((val) => { + values.forEach(val => { input = driver.findElement(this.elements.input(val.name, val.type)); if (clearPreviousValue) { input.clear(); @@ -49,7 +62,9 @@ module.exports = url => driver => ({ }, getInputValue(name, type = 'input') { - return driver.findElement(this.elements.input(name, type)).getAttribute('value'); + return driver + .findElement(this.elements.input(name, type)) + .getAttribute('value'); }, submit() { diff --git a/e2e/pages/DeletePage.js b/e2e/pages/DeletePage.js index 9bd414da..d6a496a9 100644 --- a/e2e/pages/DeletePage.js +++ b/e2e/pages/DeletePage.js @@ -17,9 +17,19 @@ module.exports = url => driver => ({ waitUntilDataLoaded() { let continued = true; - return driver.wait(until.elementLocated(this.elements.appLoader), 2000) - .catch(() => continued = false) // no loader - we're on the same page ! - .then(() => continued ? driver.wait(until.stalenessOf(driver.findElement(this.elements.appLoader))) : true) + return driver + .wait(until.elementLocated(this.elements.appLoader), 2000) + .catch(() => (continued = false)) // no loader - we're on the same page ! + .then( + () => + continued + ? driver.wait( + until.stalenessOf( + driver.findElement(this.elements.appLoader) + ) + ) + : true + ) .catch(() => {}) // The element might have disapeared before the wait on the previous line .then(() => driver.sleep(100)); // let some time to redraw }, @@ -28,5 +38,4 @@ module.exports = url => driver => ({ driver.findElement(this.elements.deleteButton).click(); return this.waitUntilDataLoaded(); }, - }); diff --git a/e2e/pages/EditPage.js b/e2e/pages/EditPage.js index 50f8c74c..a2b80f0c 100644 --- a/e2e/pages/EditPage.js +++ b/e2e/pages/EditPage.js @@ -20,9 +20,19 @@ module.exports = url => driver => ({ waitUntilDataLoaded() { let continued = true; - return driver.wait(until.elementLocated(this.elements.appLoader), 2000) - .catch(() => continued = false) // no loader - we're on the same page ! - .then(() => continued ? driver.wait(until.stalenessOf(driver.findElement(this.elements.appLoader))) : true) + return driver + .wait(until.elementLocated(this.elements.appLoader), 2000) + .catch(() => (continued = false)) // no loader - we're on the same page ! + .then( + () => + continued + ? driver.wait( + until.stalenessOf( + driver.findElement(this.elements.appLoader) + ) + ) + : true + ) .catch(() => {}) // The element might have disapeared before the wait on the previous line .then(() => driver.sleep(100)); // let some time to redraw }, diff --git a/e2e/pages/ListPage.js b/e2e/pages/ListPage.js index 56606d51..3761431b 100644 --- a/e2e/pages/ListPage.js +++ b/e2e/pages/ListPage.js @@ -6,8 +6,10 @@ module.exports = url => driver => ({ appLoader: By.css('.app-loader'), displayedRecords: By.css('.displayed-records'), filter: name => By.css(`.filter-field[data-source='${name}'] input`), - filterMenuItem: source => By.css(`.new-filter-item[data-key="${source}"]`), - hideFilterButton: source => By.css(`.filter-field[data-source="${source}"] .hide-filter`), + filterMenuItem: source => + By.css(`.new-filter-item[data-key="${source}"]`), + hideFilterButton: source => + By.css(`.filter-field[data-source="${source}"] .hide-filter`), nextPage: By.css('.next-page'), pageNumber: n => By.css(`.page-number[data-page='${n}']`), previousPage: By.css('.previous-page'), @@ -27,15 +29,26 @@ module.exports = url => driver => ({ waitUntilDataLoaded() { let continued = true; - return driver.wait(until.elementLocated(this.elements.appLoader), 2000) - .catch(() => continued = false) // no loader - we're on the same page ! - .then(() => continued ? driver.wait(until.stalenessOf(driver.findElement(this.elements.appLoader))) : true) + return driver + .wait(until.elementLocated(this.elements.appLoader), 2000) + .catch(() => (continued = false)) // no loader - we're on the same page ! + .then( + () => + continued + ? driver.wait( + until.stalenessOf( + driver.findElement(this.elements.appLoader) + ) + ) + : true + ) .catch(() => {}) // The element might have disapeared before the wait on the previous line .then(() => driver.sleep(100)); // let some time to redraw; }, getNbRows() { - return driver.findElements(this.elements.recordRows) + return driver + .findElements(this.elements.recordRows) .then(rows => rows.length); }, @@ -69,7 +82,9 @@ module.exports = url => driver => ({ }, showFilter(name) { - const addFilterButton = driver.findElement(this.elements.addFilterButton); + const addFilterButton = driver.findElement( + this.elements.addFilterButton + ); addFilterButton.click(); driver.sleep(500); // wait until the dropdown animation ends driver.wait(until.elementLocated(this.elements.filterMenuItem(name))); @@ -78,7 +93,9 @@ module.exports = url => driver => ({ }, hideFilter(name) { - const hideFilterButton = driver.findElement(this.elements.hideFilterButton(name)); + const hideFilterButton = driver.findElement( + this.elements.hideFilterButton(name) + ); hideFilterButton.click(); return this.waitUntilDataLoaded(); // wait for debounce and reload }, diff --git a/e2e/pages/LoginPage.js b/e2e/pages/LoginPage.js index ccd61f56..49b54a77 100644 --- a/e2e/pages/LoginPage.js +++ b/e2e/pages/LoginPage.js @@ -1,6 +1,6 @@ import { By, until } from 'selenium-webdriver'; -module.exports = (url) => (driver) => ({ +module.exports = url => driver => ({ elements: { username: By.css("input[name='username']"), password: By.css("input[name='password']"), diff --git a/e2e/pages/ShowPage.js b/e2e/pages/ShowPage.js index db8e6780..2a72563b 100644 --- a/e2e/pages/ShowPage.js +++ b/e2e/pages/ShowPage.js @@ -17,9 +17,19 @@ module.exports = url => driver => ({ waitUntilDataLoaded() { let continued = true; - return driver.wait(until.elementLocated(this.elements.appLoader), 2000) - .catch(() => continued = false) // no loader - we're on the same page ! - .then(() => continued ? driver.wait(until.stalenessOf(driver.findElement(this.elements.appLoader))) : true) + return driver + .wait(until.elementLocated(this.elements.appLoader), 2000) + .catch(() => (continued = false)) // no loader - we're on the same page ! + .then( + () => + continued + ? driver.wait( + until.stalenessOf( + driver.findElement(this.elements.appLoader) + ) + ) + : true + ) .catch(() => {}) // The element might have disapeared before the wait on the previous line .then(() => driver.sleep(100)); // let some time to redraw }, diff --git a/e2e/tests/auth.js b/e2e/tests/auth.js index 1273c26a..317917f1 100644 --- a/e2e/tests/auth.js +++ b/e2e/tests/auth.js @@ -9,16 +9,28 @@ describe('Authentication', () => { it('should go to login page after logout', async () => { await ListPage.navigate(); await ListPage.logout(); - return driver.wait(until.urlIs('http://localhost:8083/#/login'), 2000, 'Redirection to login did not happen'); + return driver.wait( + until.urlIs('http://localhost:8083/#/login'), + 2000, + 'Redirection to login did not happen' + ); }); it('should redirect to login page when not logged in', async () => { await ListPage.navigate(); - return driver.wait(until.urlIs('http://localhost:8083/#/login'), 2000, 'Redirection to login did not happen'); + return driver.wait( + until.urlIs('http://localhost:8083/#/login'), + 2000, + 'Redirection to login did not happen' + ); }); it('should not login with incorrect credentials', async () => { await LoginPage.navigate(); await LoginPage.login('foo', 'bar'); - return driver.wait(until.urlIs('http://localhost:8083/#/login'), 2000, 'Redirection to login did not happen'); + return driver.wait( + until.urlIs('http://localhost:8083/#/login'), + 2000, + 'Redirection to login did not happen' + ); }); it('should login with correct credentials', async () => { await LoginPage.navigate(); diff --git a/e2e/tests/create.js b/e2e/tests/create.js index 8df33221..fdb500fa 100644 --- a/e2e/tests/create.js +++ b/e2e/tests/create.js @@ -5,15 +5,22 @@ import createPageFactory from '../pages/CreatePage'; import deletePageFactory from '../pages/DeletePage'; describe('Create Page', () => { - const CreatePage = createPageFactory('http://localhost:8083/#/posts/create')(driver); - const DeletePage = deletePageFactory('http://localhost:8083/#/posts/14/delete')(driver); + const CreatePage = createPageFactory( + 'http://localhost:8083/#/posts/create' + )(driver); + const DeletePage = deletePageFactory( + 'http://localhost:8083/#/posts/14/delete' + )(driver); beforeEach(async () => await CreatePage.navigate()); it('should put the current date in the field by default', async () => { const currentDate = new Date(); const currentDateString = currentDate.toISOString().slice(0, 10); - assert.equal(await CreatePage.getInputValue('published_at'), currentDateString); + assert.equal( + await CreatePage.getInputValue('published_at'), + currentDateString + ); }); it('should redirect to show page after create success', async () => { @@ -60,11 +67,13 @@ describe('Create Page', () => { }); it('should not accept creation without required fields', async () => { - const values = [{ - type: 'textarea', - name: 'teaser', - value: 'Test teaser', - }]; + const values = [ + { + type: 'textarea', + name: 'teaser', + value: 'Test teaser', + }, + ]; await CreatePage.setValues(values); await CreatePage.submit(); }); diff --git a/e2e/tests/edit.js b/e2e/tests/edit.js index b3b81579..b273f125 100644 --- a/e2e/tests/edit.js +++ b/e2e/tests/edit.js @@ -10,7 +10,10 @@ describe('Edit Page', () => { describe('TabbedForm', () => { it('should display the title in a TextField', async () => { - assert.equal(await EditPage.getInputValue('title'), 'Sed quo et et fugiat modi'); + assert.equal( + await EditPage.getInputValue('title'), + 'Sed quo et et fugiat modi' + ); }); it('should allow to update elements', async () => { @@ -24,7 +27,10 @@ describe('Edit Page', () => { it('should redirect to list page after edit success', async () => { await EditPage.setInputValue('title', 'Lorem Ipsum +'); await EditPage.submit(); - assert.equal(await driver.getCurrentUrl(), 'http://localhost:8083/#/posts'); + assert.equal( + await driver.getCurrentUrl(), + 'http://localhost:8083/#/posts' + ); await EditPage.navigate(); }); @@ -35,9 +41,14 @@ describe('Edit Page', () => { it('should keep DateInput value after opening datapicker', async () => { await EditPage.gotoTab(3); - const valueBeforeClick = (await EditPage.getInputValue('published_at')); + const valueBeforeClick = await EditPage.getInputValue( + 'published_at' + ); await EditPage.clickInput('published_at'); - assert.equal(await EditPage.getInputValue('published_at'), valueBeforeClick); + assert.equal( + await EditPage.getInputValue('published_at'), + valueBeforeClick + ); }); }); }); diff --git a/e2e/tests/list.js b/e2e/tests/list.js index 76a48b2b..33509c43 100644 --- a/e2e/tests/list.js +++ b/e2e/tests/list.js @@ -4,8 +4,12 @@ import driver from '../chromeDriver'; import listPageFactory from '../pages/ListPage'; describe('List Page', () => { - const ListPagePosts = listPageFactory('http://localhost:8083/#/posts')(driver); - const ListPageComments = listPageFactory('http://localhost:8083/#/comments')(driver); + const ListPagePosts = listPageFactory('http://localhost:8083/#/posts')( + driver + ); + const ListPageComments = listPageFactory( + 'http://localhost:8083/#/comments' + )(driver); before(async () => await ListPagePosts.navigate()); @@ -28,31 +32,46 @@ describe('List Page', () => { describe('Filtering', () => { it('should display `alwaysOn` filters by default', async () => { - await driver.wait(until.elementLocated(ListPagePosts.elements.filter('q'))); + await driver.wait( + until.elementLocated(ListPagePosts.elements.filter('q')) + ); - const qFilter = await driver.findElements(ListPagePosts.elements.filter('q')); + const qFilter = await driver.findElements( + ListPagePosts.elements.filter('q') + ); assert.equal(qFilter.length, 1); }); it('should filter directly while typing (with some debounce)', async () => { await ListPagePosts.setFilterValue('q', 'quis culpa impedit'); assert.equal(await ListPagePosts.getNbRows(), 1); - const displayedPosts = await driver.findElements(ListPagePosts.elements.recordRows); - const title = await displayedPosts[0].findElement(By.css('.column-title')); - assert.equal(await title.getText(), 'Omnis voluptate enim similique est possimus'); + const displayedPosts = await driver.findElements( + ListPagePosts.elements.recordRows + ); + const title = await displayedPosts[0].findElement( + By.css('.column-title') + ); + assert.equal( + await title.getText(), + 'Omnis voluptate enim similique est possimus' + ); await ListPagePosts.setFilterValue('q', ''); }); it('should display new filter when clicking on "Add Filter"', async () => { await ListPagePosts.showFilter('title'); - const filters = await driver.findElements(ListPagePosts.elements.filter('title')); + const filters = await driver.findElements( + ListPagePosts.elements.filter('title') + ); assert.equal(filters.length, 1); assert.equal(await ListPagePosts.getNbPagesText(), '1-1 of 1'); }); it('should hide filter when clicking on hide button', async () => { await ListPagePosts.hideFilter('title'); - const filters = await driver.findElements(ListPagePosts.elements.filter('title')); + const filters = await driver.findElements( + ListPagePosts.elements.filter('title') + ); assert.equal(filters.length, 0); assert.equal(await ListPagePosts.getNbPagesText(), '1-10 of 13'); }); diff --git a/e2e/tests/server.js b/e2e/tests/server.js index 6e96acde..f63455e8 100644 --- a/e2e/tests/server.js +++ b/e2e/tests/server.js @@ -4,11 +4,17 @@ import driver from '../chromeDriver'; let listeningServer; -before(() => new Promise((resolve) => { - const server = express(); - server.use('/', express.static(path.join(__dirname, '../../example'))); - listeningServer = server.listen(8083, resolve); -})); +before( + () => + new Promise(resolve => { + const server = express(); + server.use( + '/', + express.static(path.join(__dirname, '../../example')) + ); + listeningServer = server.listen(8083, resolve); + }) +); after(async () => { listeningServer.close(); diff --git a/e2e/tests/show.js b/e2e/tests/show.js index 8395393a..7fee2506 100644 --- a/e2e/tests/show.js +++ b/e2e/tests/show.js @@ -3,12 +3,17 @@ import driver from '../chromeDriver'; import showPageFactory from '../pages/ShowPage'; describe('Show Page', () => { - const ShowPage = showPageFactory('http://localhost:8083/#/posts/13/show')(driver); + const ShowPage = showPageFactory('http://localhost:8083/#/posts/13/show')( + driver + ); beforeEach(async () => await ShowPage.navigate()); it('should fill the page with data from the fetched record', async () => { await ShowPage.navigate(); - assert.equal(await ShowPage.getValue('title'), 'Fusce massa lorem, pulvinar a posuere ut, accumsan ac nisi'); + assert.equal( + await ShowPage.getValue('title'), + 'Fusce massa lorem, pulvinar a posuere ut, accumsan ac nisi' + ); }); }); diff --git a/src/CrudRoute.js b/src/CrudRoute.js index 2e057b22..8fe45230 100644 --- a/src/CrudRoute.js +++ b/src/CrudRoute.js @@ -13,41 +13,47 @@ const CrudRoute = ({ resource, list, create, edit, show, remove, options }) => { hasCreate: !!create, hasDelete: !!remove, }; - const RestrictedPage = (component, route) => routeProps => - - {createElement(component, { ...commonProps, ...routeProps })} - ; + const restrictPage = (component, route) => { + const RestrictedPage = routeProps => + + {createElement(component, { + ...commonProps, + ...routeProps, + })} + ; + return RestrictedPage; + }; return ( {list && } {create && } {edit && } {show && } {remove && } ); diff --git a/src/mui/field/TextField.js b/src/mui/field/TextField.js index 8c344bc3..05c310e4 100644 --- a/src/mui/field/TextField.js +++ b/src/mui/field/TextField.js @@ -4,8 +4,12 @@ import get from 'lodash.get'; import pure from 'recompose/pure'; const TextField = ({ source, record = {}, elStyle }) => { - return {get(record, source)}; -} + return ( + + {get(record, source)} + + ); +}; TextField.propTypes = { addLabel: PropTypes.bool, diff --git a/src/mui/form/Toolbar.js b/src/mui/form/Toolbar.js index 7996f7a5..46b29c8c 100644 --- a/src/mui/form/Toolbar.js +++ b/src/mui/form/Toolbar.js @@ -15,27 +15,39 @@ const styles = { }, }; -const valueOrDefault = (value, defaultValue) => typeof value === 'undefined' ? defaultValue : value; +const valueOrDefault = (value, defaultValue) => + typeof value === 'undefined' ? defaultValue : value; -const Toolbar = ({ invalid, submitOnEnter, handleSubmitWithRedirect, children }) => ( +const Toolbar = ({ + invalid, + submitOnEnter, + handleSubmitWithRedirect, + children, +}) => {Children.count(children) === 0 ? - : Children.map(children, button => React.cloneElement(button, { - invalid, - handleSubmitWithRedirect, - raised: false, - submitOnEnter: valueOrDefault(button.props.submitOnEnter, submitOnEnter), - })) - } + handleSubmitWithRedirect={ + handleSubmitWithRedirect + } + invalid={invalid} + raised={false} + submitOnEnter={submitOnEnter} + /> + : Children.map(children, button => + React.cloneElement(button, { + invalid, + handleSubmitWithRedirect, + raised: false, + submitOnEnter: valueOrDefault( + button.props.submitOnEnter, + submitOnEnter + ), + }) + )} } @@ -44,21 +56,26 @@ const Toolbar = ({ invalid, submitOnEnter, handleSubmitWithRedirect, children }) {Children.count(children) === 0 ? - : Children.map(children, button => React.cloneElement(button, { - handleSubmitWithRedirect, - invalid, - submitOnEnter: valueOrDefault(button.props.submitOnEnter, submitOnEnter), - })) - } + handleSubmitWithRedirect={ + handleSubmitWithRedirect + } + invalid={invalid} + submitOnEnter={submitOnEnter} + /> + : Children.map(children, button => + React.cloneElement(button, { + handleSubmitWithRedirect, + invalid, + submitOnEnter: valueOrDefault( + button.props.submitOnEnter, + submitOnEnter + ), + }) + )} } - /> -); + />; Toolbar.propTypes = { children: PropTypes.node, diff --git a/src/mui/input/AutocompleteInput.js b/src/mui/input/AutocompleteInput.js index aa977a90..60bddb63 100644 --- a/src/mui/input/AutocompleteInput.js +++ b/src/mui/input/AutocompleteInput.js @@ -112,7 +112,9 @@ export class AutocompleteInput extends Component { source, } = this.props; if (typeof meta === 'undefined') { - throw new Error('The AutocompleteInput component wasn\'t called within a redux-form . Did you decorate it and forget to add the addField prop to your component? See https://marmelab.com/admin-on-rest/Inputs.html#writing-your-own-input-component for details.'); + throw new Error( + "The AutocompleteInput component wasn't called within a redux-form . Did you decorate it and forget to add the addField prop to your component? See https://marmelab.com/admin-on-rest/Inputs.html#writing-your-own-input-component for details." + ); } const { touched, error } = meta; diff --git a/src/mui/input/DateInput.js b/src/mui/input/DateInput.js index f9fe2d71..79c51aba 100644 --- a/src/mui/input/DateInput.js +++ b/src/mui/input/DateInput.js @@ -35,26 +35,46 @@ class DateInput extends Component { onDismiss = () => this.props.input.onBlur(); render() { - const { input, isRequired, label, meta, options, source, elStyle, resource } = this.props; + const { + input, + isRequired, + label, + meta, + options, + source, + elStyle, + resource, + } = this.props; if (typeof meta === 'undefined') { - throw new Error('The DateInput component wasn\'t called within a redux-form . Did you decorate it and forget to add the addField prop to your component? See https://marmelab.com/admin-on-rest/Inputs.html#writing-your-own-input-component for details.'); + throw new Error( + "The DateInput component wasn't called within a redux-form . Did you decorate it and forget to add the addField prop to your component? See https://marmelab.com/admin-on-rest/Inputs.html#writing-your-own-input-component for details." + ); } const { touched, error } = meta; - return (} - DateTimeFormat={Intl.DateTimeFormat} - container="inline" - autoOk - value={datify(input.value)} - onChange={this.onChange} - onBlur={this.onBlur} - onDismiss={this.onDismiss} - style={elStyle} - {...options} - />); + return ( + + } + DateTimeFormat={Intl.DateTimeFormat} + container="inline" + autoOk + value={datify(input.value)} + onChange={this.onChange} + onBlur={this.onBlur} + onDismiss={this.onDismiss} + style={elStyle} + {...options} + /> + ); } } diff --git a/src/mui/input/FileInput.js b/src/mui/input/FileInput.js index bc2c337b..b771d2e3 100644 --- a/src/mui/input/FileInput.js +++ b/src/mui/input/FileInput.js @@ -34,6 +34,7 @@ export class FileInput extends Component { multiple: PropTypes.bool, removeStyle: PropTypes.object, style: PropTypes.object, + translate: PropTypes.func.isRequired, placeholder: PropTypes.node, }; @@ -69,30 +70,33 @@ export class FileInput extends Component { this.setState({ files: files.map(this.transformFile) }); } - onDrop = (files) => { + onDrop = files => { const updatedFiles = this.props.multiple ? [...this.state.files, ...files.map(this.transformFile)] : [...files.map(this.transformFile)]; this.setState({ files: updatedFiles }); this.props.input.onChange(updatedFiles); - } + }; onRemove = file => () => { - const filteredFiles = this.state.files - .filter(stateFile => !shallowEqual(stateFile, file)); + const filteredFiles = this.state.files.filter( + stateFile => !shallowEqual(stateFile, file) + ); this.setState({ files: filteredFiles }); this.props.input.onChange(filteredFiles); - } + }; // turn a browser dropped file structure into expected structure - transformFile = (file) => { + transformFile = file => { if (!(file instanceof File)) { return file; } - const { source, title } = React.Children.toArray(this.props.children)[0].props; + const { source, title } = React.Children.toArray( + this.props.children + )[0].props; const transformedFile = { rawFile: file, @@ -107,21 +111,22 @@ export class FileInput extends Component { }; label() { - const { translate, placeholder, labelMultiple, labelSingle } = this.props; + const { + translate, + placeholder, + labelMultiple, + labelSingle, + } = this.props; if (placeholder) { return placeholder; } if (this.props.multiple) { - return ( -

{translate(labelMultiple)}

- ); + return

{translate(labelMultiple)}

; } - return ( -

{translate(labelSingle)}

- ); + return

{translate(labelSingle)}

; } render() { @@ -156,7 +161,7 @@ export class FileInput extends Component { > {this.label()} - { children && ( + {children && (
{this.state.files.map((file, index) => ( ))}
- ) } + )}
); } diff --git a/src/mui/input/FileInputPreview.spec.js b/src/mui/input/FileInputPreview.spec.js index d9bba910..226b90b7 100644 --- a/src/mui/input/FileInputPreview.spec.js +++ b/src/mui/input/FileInputPreview.spec.js @@ -9,11 +9,11 @@ describe('', () => { it('should call `onRemove` prop when clicking on remove button', () => { const onRemoveSpy = sinon.spy(); - const wrapper = shallow(( + const wrapper = shallow(
Child
- )); + ); const removeButton = wrapper.find('IconButton'); removeButton.simulate('click'); @@ -22,11 +22,11 @@ describe('', () => { }); it('should render passed children', () => { - const wrapper = shallow(( + const wrapper = shallow(
Child
- )); + ); const child = wrapper.find('#child'); assert.equal(child.length, 1); @@ -41,11 +41,12 @@ describe('', () => { revokeObjectURL, }, }; - const wrapper = shallow(( + const wrapper = shallow(
Child
-
- ), { lifecycleExperimental: true }); +
, + { lifecycleExperimental: true } + ); wrapper.unmount(); assert(revokeObjectURL.calledWith('previewUrl')); @@ -60,11 +61,12 @@ describe('', () => { revokeObjectURL, }, }; - const wrapper = shallow(( + const wrapper = shallow(
Child
-
- ), { lifecycleExperimental: true }); +
, + { lifecycleExperimental: true } + ); wrapper.unmount(); assert(revokeObjectURL.notCalled); diff --git a/src/mui/input/ImageInput.spec.js b/src/mui/input/ImageInput.spec.js index 01b2f44e..cd00bbd1 100644 --- a/src/mui/input/ImageInput.spec.js +++ b/src/mui/input/ImageInput.spec.js @@ -13,7 +13,7 @@ describe('', () => { }); it('should display a dropzone', () => { - const wrapper = shallow(( + const wrapper = shallow( ', () => { translate={x => x} source="picture" /> - )); + ); assert.equal(wrapper.find('Dropzone').length, 1); }); it('should display correct label depending multiple property', () => { const test = (multiple, expectedLabel) => { - const wrapper = shallow(( + const wrapper = shallow( ', () => { translate={x => x} source="picture" /> - )); + ); assert.equal(wrapper.find('Dropzone p').text(), expectedLabel); }; @@ -51,8 +51,8 @@ describe('', () => { }); it('should display correct custom label', () => { - const test = (expectedLabel) => { - const wrapper = shallow(( + const test = expectedLabel => { + const wrapper = shallow( ', () => { translate={x => x} source="picture" /> - )); + ); assert.ok(wrapper.find('Dropzone').contains(expectedLabel)); }; - const CustomLabel = () => ( -
Custom label
- ); + const CustomLabel = () =>
Custom label
; test('custom label'); test(

Custom label

); @@ -78,7 +76,7 @@ describe('', () => { describe('Image Preview', () => { it('should display file preview using child as preview component', () => { - const wrapper = shallow(( + const wrapper = shallow( ', () => { > - )); + ); const previewImage = wrapper.find('ImageField'); @@ -104,32 +102,50 @@ describe('', () => { }); it('should display all files (when several) previews using child as preview component', () => { - const wrapper = shallow(( + const wrapper = shallow( x} > - )); + ); const previewImages = wrapper.find('ImageField'); assert.equal(previewImages.length, 2); assert.equal(previewImages.at(0).prop('source'), 'url'); assert.equal(previewImages.at(0).prop('title'), 'title'); - assert.deepEqual(previewImages.at(0).prop('record').title, 'Hello world!'); - assert.deepEqual(previewImages.at(0).prop('record').url, 'http://foo.com/bar.jpg'); + assert.deepEqual( + previewImages.at(0).prop('record').title, + 'Hello world!' + ); + assert.deepEqual( + previewImages.at(0).prop('record').url, + 'http://foo.com/bar.jpg' + ); assert.equal(previewImages.at(1).prop('source'), 'url'); assert.equal(previewImages.at(1).prop('title'), 'title'); - assert.deepEqual(previewImages.at(1).prop('record').title, 'A good old Bitmap!'); - assert.deepEqual(previewImages.at(1).prop('record').url, 'http://foo.com/qux.bmp'); + assert.deepEqual( + previewImages.at(1).prop('record').title, + 'A good old Bitmap!' + ); + assert.deepEqual( + previewImages.at(1).prop('record').url, + 'http://foo.com/qux.bmp' + ); }); it('should update previews when updating input value', () => { @@ -144,7 +160,7 @@ describe('', () => { }} > - , + ); const previewImage = wrapper.find('ImageField'); @@ -168,13 +184,9 @@ describe('', () => { it('should update previews when dropping a file', () => { const wrapper = shallow( - x} - input={{}} - > + x} input={{}}> - , + ); wrapper.setProps({ @@ -208,14 +220,16 @@ describe('', () => { }} > - , + ); const inputPreview = wrapper.find('FileInputPreview'); inputPreview.at(1).prop('onRemove')(); wrapper.update(); - const previewImages = wrapper.find('ImageField').map(f => f.prop('record')); + const previewImages = wrapper + .find('ImageField') + .map(f => f.prop('record')); assert.deepEqual(previewImages, [ { url: 'http://static.acme.com/foo.jpg' }, { url: 'http://static.acme.com/quz.jpg' }, diff --git a/src/mui/input/SelectArrayInput.js b/src/mui/input/SelectArrayInput.js index e2aeacfc..59ba0f81 100644 --- a/src/mui/input/SelectArrayInput.js +++ b/src/mui/input/SelectArrayInput.js @@ -174,7 +174,9 @@ export class SelectArrayInput extends Component { translateChoice, } = this.props; if (typeof meta === 'undefined') { - throw new Error('The SelectArrayInput component wasn\'t called within a redux-form . Did you decorate it and forget to add the addField prop to your component? See https://marmelab.com/admin-on-rest/Inputs.html#writing-your-own-input-component for details.'); + throw new Error( + "The SelectArrayInput component wasn't called within a redux-form . Did you decorate it and forget to add the addField prop to your component? See https://marmelab.com/admin-on-rest/Inputs.html#writing-your-own-input-component for details." + ); } const { touched, error } = meta; diff --git a/src/mui/input/TextInput.js b/src/mui/input/TextInput.js index 7f7d0ed7..202403d7 100644 --- a/src/mui/input/TextInput.js +++ b/src/mui/input/TextInput.js @@ -46,7 +46,9 @@ export class TextInput extends Component { type, } = this.props; if (typeof meta === 'undefined') { - throw new Error('The TextInput component wasn\'t called within a redux-form . Did you decorate it and forget to add the addField prop to your component? See https://marmelab.com/admin-on-rest/Inputs.html#writing-your-own-input-component for details.'); + throw new Error( + "The TextInput component wasn't called within a redux-form . Did you decorate it and forget to add the addField prop to your component? See https://marmelab.com/admin-on-rest/Inputs.html#writing-your-own-input-component for details." + ); } const { touched, error } = meta; diff --git a/src/util/fetch.spec.js b/src/util/fetch.spec.js index 59b10d6b..40504533 100644 --- a/src/util/fetch.spec.js +++ b/src/util/fetch.spec.js @@ -10,7 +10,9 @@ describe('queryParameters', () => { it('should generate multiple query parameters', () => { const data = { foo: 'fooval', bar: 'barval' }; const actual = queryParameters(data); - assert(['foo=fooval&bar=barval', 'bar=barval&foo=fooval'].includes(actual)); + assert( + ['foo=fooval&bar=barval', 'bar=barval&foo=fooval'].includes(actual) + ); }); it('should generate multiple query parameters with a same name', () => { @@ -20,6 +22,9 @@ describe('queryParameters', () => { it('should generate an encoded query parameter', () => { const data = ['foo=bar', 'foo?bar&baz']; - assert.equal(queryParameters({ [data[0]]: data[1] }), data.map(encodeURIComponent).join('=')); + assert.equal( + queryParameters({ [data[0]]: data[1] }), + data.map(encodeURIComponent).join('=') + ); }); }); From ecd6681e896539609ed951d00a463a8152de0022 Mon Sep 17 00:00:00 2001 From: Francois Zaninotto Date: Mon, 3 Jul 2017 15:47:53 +0200 Subject: [PATCH 05/65] Add make prettier command --- CONTRIBUTING.md | 8 +++++++- Makefile | 3 +++ README.md | 20 +++++++++++++------- package.json | 1 + src/mui/layout/MenuItemLink.js | 2 +- 5 files changed, 25 insertions(+), 9 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e216e965..19ba9f90 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -49,7 +49,13 @@ At any given time, `next` represents the latest development version of the libra ### Coding style -Please follow the coding style of the current code base. Admin-on-rest uses eslint, so if possible, enable linting in your editor to get realtime feedback. +You must follow the coding style of the existing files. Admin-on-rest uses eslint and [prettier](https://github.com/prettier/prettier). You can reformat all the project files automatically by calling + +```sh +make prettier +``` + +**Tip**: If possible, enable linting in your editor to get realtime feedback and/or fixes. ## License diff --git a/Makefile b/Makefile index 61085c65..17b7e7d0 100644 --- a/Makefile +++ b/Makefile @@ -24,6 +24,9 @@ doc: ## compile doc as html and launch doc web server lint: ## lint the code and check coding conventions @./node_modules/.bin/eslint . +prettier: ## prettify the source code using prettier + @./node_modules/.bin/prettier-eslint --write --list-different "src/**/*.js" "example/**/*.js" + test: lint test-unit test-e2e ## launch all tests test-unit: ## launch unit tests diff --git a/README.md b/README.md index 6bac7397..1ff55709 100644 --- a/README.md +++ b/README.md @@ -150,24 +150,30 @@ The credentials are **login/password** ## Contributing -Pull requests are welcome. Try to follow the coding style of the existing files, and include unit tests and documentation. Be prepared for a thorough code review, and be patient for the merge - this is an open-source initiative. +Pull requests are welcome. You must follow the coding style of the existing files (based on [prettier](https://github.com/prettier/prettier)), and include unit tests and documentation. Be prepared for a thorough code review, and be patient for the merge - this is an open-source initiative. -If you want to contribute to the documentation, install jekyll, then call +You can run the tests (linting, unit and functional tests) by calling ```sh -make doc +make test ``` -And then browse to [http://localhost:4000/](http://localhost:4000/) +If you have coding standards problems, you can fix them automatically using `prettier` by calling -*Note*: if you have added a section with heading to the docs, you also have to add it to `docs/_layouts/default.html` (the links on the left) manually. +```sh +make prettier +``` -You can run the unit tests by calling +If you want to contribute to the documentation, install jekyll, then call ```sh -make test +make doc ``` +And then browse to [http://localhost:4000/](http://localhost:4000/) + +*Note*: if you have added a section with heading to the docs, you also have to add it to `docs/_layouts/default.html` (the links on the left) manually. + If you are using admin-on-rest as a dependency, and if you want to try and hack it, here is the advised process: ```sh diff --git a/package.json b/package.json index 04e7bcd5..28572d1b 100644 --- a/package.json +++ b/package.json @@ -48,6 +48,7 @@ "ignore-styles": "~5.0.1", "mocha": "~3.2.0", "prettier": "^1.5.2", + "prettier-eslint-cli": "^4.1.1", "react-addons-test-utils": "~15.5.1", "selenium-webdriver": "~3.3.0", "sinon": "~2.1.0", diff --git a/src/mui/layout/MenuItemLink.js b/src/mui/layout/MenuItemLink.js index 9b326825..a153f67c 100644 --- a/src/mui/layout/MenuItemLink.js +++ b/src/mui/layout/MenuItemLink.js @@ -22,7 +22,7 @@ export class MenuItemLinkComponent extends Component { location, staticContext, ...props - } = this.props; // eslint-disable-line + } = this.props; return ; } From cdacf4eb8c6752c5f540f5c62c36e3c1c295821f Mon Sep 17 00:00:00 2001 From: Francois Zaninotto Date: Mon, 3 Jul 2017 15:50:41 +0200 Subject: [PATCH 06/65] run unit tests before linter --- Makefile | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 17b7e7d0..4913b2a9 100644 --- a/Makefile +++ b/Makefile @@ -22,14 +22,16 @@ doc: ## compile doc as html and launch doc web server @cd docs && jekyll server . --watch lint: ## lint the code and check coding conventions + @echo "Running linter..." @./node_modules/.bin/eslint . prettier: ## prettify the source code using prettier @./node_modules/.bin/prettier-eslint --write --list-different "src/**/*.js" "example/**/*.js" -test: lint test-unit test-e2e ## launch all tests +test: test-unit lint test-e2e ## launch all tests test-unit: ## launch unit tests + @echo "Running unit tests..." @NODE_ENV=test NODE_ICU_DATA=node_modules/full-icu ./node_modules/.bin/mocha \ --require ignore-styles \ --compilers js:babel-register \ From 3caaa89e8465d14b57498e8b505fa44ced9a0eba Mon Sep 17 00:00:00 2001 From: lutangar Date: Wed, 3 May 2017 15:53:47 +0200 Subject: [PATCH 07/65] create admin reducer directory and move related reducers into it --- src/reducer/{ => admin}/index.js | 0 src/reducer/{ => admin}/loading.js | 0 src/reducer/{ => admin}/loading.spec.js | 0 src/reducer/{ => admin}/notification.js | 0 src/reducer/{ => admin}/notification.spec.js | 0 src/reducer/{ => admin}/references/index.js | 0 src/reducer/{ => admin}/references/oneToMany.js | 0 src/reducer/{ => admin}/references/possibleValues.js | 0 src/reducer/{ => admin}/resource/data.js | 0 src/reducer/{ => admin}/resource/index.js | 0 src/reducer/{ => admin}/resource/list/ids.js | 0 src/reducer/{ => admin}/resource/list/index.js | 0 src/reducer/{ => admin}/resource/list/params.js | 0 src/reducer/{ => admin}/resource/list/queryReducer.js | 0 src/reducer/{ => admin}/resource/list/queryReducer.spec.js | 0 src/reducer/{ => admin}/resource/list/total.js | 0 src/reducer/{ => admin}/saving.js | 0 src/reducer/{ => admin}/ui.js | 0 src/reducer/{ => admin}/ui.spec.js | 0 19 files changed, 0 insertions(+), 0 deletions(-) rename src/reducer/{ => admin}/index.js (100%) rename src/reducer/{ => admin}/loading.js (100%) rename src/reducer/{ => admin}/loading.spec.js (100%) rename src/reducer/{ => admin}/notification.js (100%) rename src/reducer/{ => admin}/notification.spec.js (100%) rename src/reducer/{ => admin}/references/index.js (100%) rename src/reducer/{ => admin}/references/oneToMany.js (100%) rename src/reducer/{ => admin}/references/possibleValues.js (100%) rename src/reducer/{ => admin}/resource/data.js (100%) rename src/reducer/{ => admin}/resource/index.js (100%) rename src/reducer/{ => admin}/resource/list/ids.js (100%) rename src/reducer/{ => admin}/resource/list/index.js (100%) rename src/reducer/{ => admin}/resource/list/params.js (100%) rename src/reducer/{ => admin}/resource/list/queryReducer.js (100%) rename src/reducer/{ => admin}/resource/list/queryReducer.spec.js (100%) rename src/reducer/{ => admin}/resource/list/total.js (100%) rename src/reducer/{ => admin}/saving.js (100%) rename src/reducer/{ => admin}/ui.js (100%) rename src/reducer/{ => admin}/ui.spec.js (100%) diff --git a/src/reducer/index.js b/src/reducer/admin/index.js similarity index 100% rename from src/reducer/index.js rename to src/reducer/admin/index.js diff --git a/src/reducer/loading.js b/src/reducer/admin/loading.js similarity index 100% rename from src/reducer/loading.js rename to src/reducer/admin/loading.js diff --git a/src/reducer/loading.spec.js b/src/reducer/admin/loading.spec.js similarity index 100% rename from src/reducer/loading.spec.js rename to src/reducer/admin/loading.spec.js diff --git a/src/reducer/notification.js b/src/reducer/admin/notification.js similarity index 100% rename from src/reducer/notification.js rename to src/reducer/admin/notification.js diff --git a/src/reducer/notification.spec.js b/src/reducer/admin/notification.spec.js similarity index 100% rename from src/reducer/notification.spec.js rename to src/reducer/admin/notification.spec.js diff --git a/src/reducer/references/index.js b/src/reducer/admin/references/index.js similarity index 100% rename from src/reducer/references/index.js rename to src/reducer/admin/references/index.js diff --git a/src/reducer/references/oneToMany.js b/src/reducer/admin/references/oneToMany.js similarity index 100% rename from src/reducer/references/oneToMany.js rename to src/reducer/admin/references/oneToMany.js diff --git a/src/reducer/references/possibleValues.js b/src/reducer/admin/references/possibleValues.js similarity index 100% rename from src/reducer/references/possibleValues.js rename to src/reducer/admin/references/possibleValues.js diff --git a/src/reducer/resource/data.js b/src/reducer/admin/resource/data.js similarity index 100% rename from src/reducer/resource/data.js rename to src/reducer/admin/resource/data.js diff --git a/src/reducer/resource/index.js b/src/reducer/admin/resource/index.js similarity index 100% rename from src/reducer/resource/index.js rename to src/reducer/admin/resource/index.js diff --git a/src/reducer/resource/list/ids.js b/src/reducer/admin/resource/list/ids.js similarity index 100% rename from src/reducer/resource/list/ids.js rename to src/reducer/admin/resource/list/ids.js diff --git a/src/reducer/resource/list/index.js b/src/reducer/admin/resource/list/index.js similarity index 100% rename from src/reducer/resource/list/index.js rename to src/reducer/admin/resource/list/index.js diff --git a/src/reducer/resource/list/params.js b/src/reducer/admin/resource/list/params.js similarity index 100% rename from src/reducer/resource/list/params.js rename to src/reducer/admin/resource/list/params.js diff --git a/src/reducer/resource/list/queryReducer.js b/src/reducer/admin/resource/list/queryReducer.js similarity index 100% rename from src/reducer/resource/list/queryReducer.js rename to src/reducer/admin/resource/list/queryReducer.js diff --git a/src/reducer/resource/list/queryReducer.spec.js b/src/reducer/admin/resource/list/queryReducer.spec.js similarity index 100% rename from src/reducer/resource/list/queryReducer.spec.js rename to src/reducer/admin/resource/list/queryReducer.spec.js diff --git a/src/reducer/resource/list/total.js b/src/reducer/admin/resource/list/total.js similarity index 100% rename from src/reducer/resource/list/total.js rename to src/reducer/admin/resource/list/total.js diff --git a/src/reducer/saving.js b/src/reducer/admin/saving.js similarity index 100% rename from src/reducer/saving.js rename to src/reducer/admin/saving.js diff --git a/src/reducer/ui.js b/src/reducer/admin/ui.js similarity index 100% rename from src/reducer/ui.js rename to src/reducer/admin/ui.js diff --git a/src/reducer/ui.spec.js b/src/reducer/admin/ui.spec.js similarity index 100% rename from src/reducer/ui.spec.js rename to src/reducer/admin/ui.spec.js From dec16318fe548d1995e1c95c18920d50a40fc088 Mon Sep 17 00:00:00 2001 From: lutangar Date: Tue, 4 Jul 2017 13:38:29 +0200 Subject: [PATCH 08/65] update imports and add resources level to the admin state --- src/Admin.js | 20 ++++--------------- src/index.js | 5 +++-- src/mui/delete/Delete.js | 2 +- src/mui/detail/Edit.js | 2 +- src/mui/detail/Show.js | 2 +- src/mui/field/ReferenceArrayField.js | 2 +- src/mui/field/ReferenceField.js | 4 +++- src/mui/field/ReferenceManyField.js | 7 +++++-- src/mui/input/ReferenceArrayInput.js | 4 ++-- src/mui/input/ReferenceInput.js | 5 +++-- src/mui/list/List.js | 4 ++-- src/reducer/admin/index.js | 2 +- src/reducer/admin/loading.js | 5 +++-- src/reducer/admin/loading.spec.js | 6 ++++-- src/reducer/admin/notification.js | 2 +- src/reducer/admin/notification.spec.js | 2 +- src/reducer/admin/references/oneToMany.js | 6 +++--- .../admin/references/possibleValues.js | 4 ++-- src/reducer/admin/resource/data.js | 4 ++-- src/reducer/admin/resource/list/ids.js | 2 +- src/reducer/admin/resource/list/params.js | 2 +- src/reducer/admin/resource/list/total.js | 2 +- src/reducer/admin/saving.js | 2 +- src/reducer/admin/ui.js | 2 +- src/reducer/admin/ui.spec.js | 2 +- src/reducer/index.js | 14 +++++++++++++ 26 files changed, 63 insertions(+), 51 deletions(-) create mode 100644 src/reducer/index.js diff --git a/src/Admin.js b/src/Admin.js index 2146a20a..0a46e06a 100644 --- a/src/Admin.js +++ b/src/Admin.js @@ -1,21 +1,15 @@ import React, { createElement } from 'react'; import PropTypes from 'prop-types'; -import { combineReducers, createStore, compose, applyMiddleware } from 'redux'; +import { createStore, compose, applyMiddleware } from 'redux'; import { Provider } from 'react-redux'; import createHistory from 'history/createHashHistory'; import { Switch, Route } from 'react-router-dom'; -import { - ConnectedRouter, - routerReducer, - routerMiddleware, -} from 'react-router-redux'; -import { reducer as formReducer } from 'redux-form'; +import { ConnectedRouter, routerMiddleware } from 'react-router-redux'; import createSagaMiddleware from 'redux-saga'; import { all, fork } from 'redux-saga/effects'; import { USER_LOGOUT } from './actions/authActions'; -import adminReducer from './reducer'; -import localeReducer from './reducer/locale'; +import createAppReducer from './reducer'; import { crudSaga } from './sideEffect/saga'; import DefaultLayout from './mui/layout/Layout'; import Menu from './mui/layout/Menu'; @@ -43,13 +37,7 @@ const Admin = ({ initialState, }) => { const resources = React.Children.map(children, ({ props }) => props) || []; - const appReducer = combineReducers({ - admin: adminReducer(resources), - locale: localeReducer(locale), - form: formReducer, - routing: routerReducer, - ...customReducers, - }); + const appReducer = createAppReducer(resources, customReducers, locale); const resettableAppReducer = (state, action) => appReducer(action.type !== USER_LOGOUT ? state : undefined, action); const saga = function* rootSaga() { diff --git a/src/index.js b/src/index.js index b49ea6ae..43d4f210 100644 --- a/src/index.js +++ b/src/index.js @@ -2,9 +2,10 @@ export * from './actions'; export * from './auth'; export * from './i18n'; export * from './mui'; -export adminReducer from './reducer'; +export createAppReducer from './reducer'; +export adminReducer from './reducer/admin'; export localeReducer from './reducer/locale'; -export queryReducer from './reducer/resource/list/queryReducer'; +export queryReducer from './reducer/admin/resource/list/queryReducer'; export * from './rest'; export * from './sideEffect/saga'; export * as fetchUtils from './util/fetch'; diff --git a/src/mui/delete/Delete.js b/src/mui/delete/Delete.js index f780a1d1..626ac909 100644 --- a/src/mui/delete/Delete.js +++ b/src/mui/delete/Delete.js @@ -136,7 +136,7 @@ function mapStateToProps(state, props) { return { id: decodeURIComponent(props.match.params.id), data: - state.admin[props.resource].data[ + state.admin.resources[props.resource].data[ decodeURIComponent(props.match.params.id) ], isLoading: state.admin.loading > 0, diff --git a/src/mui/detail/Edit.js b/src/mui/detail/Edit.js index 4045bb74..374853ee 100644 --- a/src/mui/detail/Edit.js +++ b/src/mui/detail/Edit.js @@ -158,7 +158,7 @@ function mapStateToProps(state, props) { return { id: decodeURIComponent(props.match.params.id), data: - state.admin[props.resource].data[ + state.admin.resources[props.resource].data[ decodeURIComponent(props.match.params.id) ], isLoading: state.admin.loading > 0, diff --git a/src/mui/detail/Show.js b/src/mui/detail/Show.js index a57566f9..3c29275b 100644 --- a/src/mui/detail/Show.js +++ b/src/mui/detail/Show.js @@ -110,7 +110,7 @@ function mapStateToProps(state, props) { return { id: decodeURIComponent(props.match.params.id), data: - state.admin[props.resource].data[ + state.admin.resources[props.resource].data[ decodeURIComponent(props.match.params.id) ], isLoading: state.admin.loading > 0, diff --git a/src/mui/field/ReferenceArrayField.js b/src/mui/field/ReferenceArrayField.js index f17e4f64..dcee2ef0 100644 --- a/src/mui/field/ReferenceArrayField.js +++ b/src/mui/field/ReferenceArrayField.js @@ -5,7 +5,7 @@ import LinearProgress from 'material-ui/LinearProgress'; import get from 'lodash.get'; import { crudGetManyAccumulate as crudGetManyAccumulateAction } from '../../actions/accumulateActions'; -import { getReferencesByIds } from '../../reducer/references/oneToMany'; +import { getReferencesByIds } from '../../reducer/admin/references/oneToMany'; /** * A container component that fetches records from another resource specified diff --git a/src/mui/field/ReferenceField.js b/src/mui/field/ReferenceField.js index 38008bd4..f599b08d 100644 --- a/src/mui/field/ReferenceField.js +++ b/src/mui/field/ReferenceField.js @@ -128,7 +128,9 @@ ReferenceField.defaultProps = { function mapStateToProps(state, props) { return { referenceRecord: - state.admin[props.reference].data[get(props.record, props.source)], + state.admin.resources[props.reference].data[ + get(props.record, props.source) + ], }; } diff --git a/src/mui/field/ReferenceManyField.js b/src/mui/field/ReferenceManyField.js index 62e5b127..192313d0 100644 --- a/src/mui/field/ReferenceManyField.js +++ b/src/mui/field/ReferenceManyField.js @@ -7,8 +7,11 @@ import { getIds, getReferences, nameRelatedTo, -} from '../../reducer/references/oneToMany'; -import { SORT_ASC, SORT_DESC } from '../../reducer/resource/list/queryReducer'; +} from '../../reducer/admin/references/oneToMany'; +import { + SORT_ASC, + SORT_DESC, +} from '../../reducer/admin/resource/list/queryReducer'; /** * Render related records to the current one. diff --git a/src/mui/input/ReferenceArrayInput.js b/src/mui/input/ReferenceArrayInput.js index be63918d..14129cad 100644 --- a/src/mui/input/ReferenceArrayInput.js +++ b/src/mui/input/ReferenceArrayInput.js @@ -7,7 +7,7 @@ import { crudGetMany as crudGetManyAction, crudGetMatching as crudGetMatchingAction, } from '../../actions/dataActions'; -import { getPossibleReferences } from '../../reducer/references/possibleValues'; +import { getPossibleReferences } from '../../reducer/admin/references/possibleValues'; const referenceSource = (resource, source) => `${resource}@${source}`; @@ -244,7 +244,7 @@ ReferenceArrayInput.defaultProps = { function mapStateToProps(state, props) { const referenceIds = props.input.value || []; - const data = state.admin[props.reference].data; + const data = state.admin.resources[props.reference].data; return { referenceRecords: referenceIds.reduce((references, referenceId) => { if (data[referenceId]) { diff --git a/src/mui/input/ReferenceInput.js b/src/mui/input/ReferenceInput.js index 9a09528e..206f29f9 100644 --- a/src/mui/input/ReferenceInput.js +++ b/src/mui/input/ReferenceInput.js @@ -7,7 +7,7 @@ import { crudGetOne as crudGetOneAction, crudGetMatching as crudGetMatchingAction, } from '../../actions/dataActions'; -import { getPossibleReferences } from '../../reducer/references/possibleValues'; +import { getPossibleReferences } from '../../reducer/admin/references/possibleValues'; const referenceSource = (resource, source) => `${resource}@${source}`; const noFilter = () => true; @@ -241,7 +241,8 @@ ReferenceInput.defaultProps = { function mapStateToProps(state, props) { const referenceId = props.input.value; return { - referenceRecord: state.admin[props.reference].data[referenceId], + referenceRecord: + state.admin.resources[props.reference].data[referenceId], matchingReferences: getPossibleReferences( state, referenceSource(props.resource, props.source), diff --git a/src/mui/list/List.js b/src/mui/list/List.js index 2d41815b..469410d7 100644 --- a/src/mui/list/List.js +++ b/src/mui/list/List.js @@ -14,7 +14,7 @@ import queryReducer, { SET_PAGE, SET_FILTER, SORT_DESC, -} from '../../reducer/resource/list/queryReducer'; +} from '../../reducer/admin/resource/list/queryReducer'; import ViewTitle from '../layout/ViewTitle'; import Title from '../layout/Title'; import DefaultPagination from './Pagination'; @@ -346,7 +346,7 @@ const getQuery = createSelector(getLocationSearch, locationSearch => { }); function mapStateToProps(state, props) { - const resourceState = state.admin[props.resource]; + const resourceState = state.admin.resources[props.resource]; return { query: getQuery(props), params: resourceState.list.params, diff --git a/src/reducer/admin/index.js b/src/reducer/admin/index.js index 3ea1209c..c9b4300a 100644 --- a/src/reducer/admin/index.js +++ b/src/reducer/admin/index.js @@ -15,7 +15,7 @@ export default resources => { ); }); return combineReducers({ - ...resourceReducers, + resources: combineReducers(resourceReducers), loading, notification, references, diff --git a/src/reducer/admin/loading.js b/src/reducer/admin/loading.js index 58ade724..44ed513c 100644 --- a/src/reducer/admin/loading.js +++ b/src/reducer/admin/loading.js @@ -3,12 +3,13 @@ import { FETCH_END, FETCH_ERROR, FETCH_CANCEL, -} from '../actions/fetchActions'; +} from '../../actions/fetchActions'; + import { USER_LOGIN_LOADING, USER_LOGIN_SUCCESS, USER_LOGIN_FAILURE, -} from '../actions/authActions'; +} from '../../actions/authActions'; export default (previousState = 0, { type }) => { switch (type) { diff --git a/src/reducer/admin/loading.spec.js b/src/reducer/admin/loading.spec.js index 57e17cdf..e4c3cef3 100644 --- a/src/reducer/admin/loading.spec.js +++ b/src/reducer/admin/loading.spec.js @@ -4,12 +4,14 @@ import { FETCH_END, FETCH_ERROR, FETCH_CANCEL, -} from '../actions/fetchActions'; +} from '../../actions/fetchActions'; + import { USER_LOGIN_LOADING, USER_LOGIN_SUCCESS, USER_LOGIN_FAILURE, -} from '../actions/authActions'; +} from '../../actions/authActions'; + import reducer from './loading'; describe('loading reducer', () => { diff --git a/src/reducer/admin/notification.js b/src/reducer/admin/notification.js index 0ae0305e..ad12ccf8 100644 --- a/src/reducer/admin/notification.js +++ b/src/reducer/admin/notification.js @@ -1,7 +1,7 @@ import { SHOW_NOTIFICATION, HIDE_NOTIFICATION, -} from '../actions/notificationActions'; +} from '../../actions/notificationActions'; const defaultState = { text: '', diff --git a/src/reducer/admin/notification.spec.js b/src/reducer/admin/notification.spec.js index a5e4c7cb..34bdffc2 100644 --- a/src/reducer/admin/notification.spec.js +++ b/src/reducer/admin/notification.spec.js @@ -2,7 +2,7 @@ import assert from 'assert'; import { SHOW_NOTIFICATION, HIDE_NOTIFICATION, -} from '../actions/notificationActions'; +} from '../../actions/notificationActions'; import reducer from './notification'; describe('notification reducer', () => { diff --git a/src/reducer/admin/references/oneToMany.js b/src/reducer/admin/references/oneToMany.js index 0ae708df..3bfac0e3 100644 --- a/src/reducer/admin/references/oneToMany.js +++ b/src/reducer/admin/references/oneToMany.js @@ -1,4 +1,4 @@ -import { CRUD_GET_MANY_REFERENCE_SUCCESS } from '../../actions/dataActions'; +import { CRUD_GET_MANY_REFERENCE_SUCCESS } from '../../../actions/dataActions'; const initialState = {}; @@ -21,7 +21,7 @@ export const getReferences = (state, reference, relatedTo) => { const ids = getIds(state, relatedTo); if (typeof ids === 'undefined') return undefined; return ids - .map(id => state.admin[reference].data[id]) + .map(id => state.admin.resources[reference].data[id]) .filter(r => typeof r !== 'undefined') .reduce((prev, record) => { prev[record.id] = record; // eslint-disable-line no-param-reassign @@ -32,7 +32,7 @@ export const getReferences = (state, reference, relatedTo) => { export const getReferencesByIds = (state, reference, ids) => { if (ids.length === 0) return {}; return ids - .map(id => state.admin[reference].data[id]) + .map(id => state.admin.resources[reference].data[id]) .filter(r => typeof r !== 'undefined') .reduce((prev, record) => { prev[record.id] = record; // eslint-disable-line no-param-reassign diff --git a/src/reducer/admin/references/possibleValues.js b/src/reducer/admin/references/possibleValues.js index 529203a2..b7d95075 100644 --- a/src/reducer/admin/references/possibleValues.js +++ b/src/reducer/admin/references/possibleValues.js @@ -1,4 +1,4 @@ -import { CRUD_GET_MATCHING_SUCCESS } from '../../actions/dataActions'; +import { CRUD_GET_MATCHING_SUCCESS } from '../../../actions/dataActions'; const initialState = {}; @@ -32,6 +32,6 @@ export const getPossibleReferences = ( ); return possibleValues - .map(id => state.admin[reference].data[id]) + .map(id => state.admin.resources[reference].data[id]) .filter(r => typeof r !== 'undefined'); }; diff --git a/src/reducer/admin/resource/data.js b/src/reducer/admin/resource/data.js index 8e8a03f2..e428b4be 100644 --- a/src/reducer/admin/resource/data.js +++ b/src/reducer/admin/resource/data.js @@ -1,4 +1,4 @@ -import { FETCH_END } from '../../actions/fetchActions'; +import { FETCH_END } from '../../../actions/fetchActions'; import { GET_LIST, GET_ONE, @@ -6,7 +6,7 @@ import { GET_MANY_REFERENCE, CREATE, UPDATE, -} from '../../rest/types'; +} from '../../../rest/types'; /** * The data state is an instance pool, which keeps track of the fetch date of each instance. diff --git a/src/reducer/admin/resource/list/ids.js b/src/reducer/admin/resource/list/ids.js index bd974cce..dd14a1b4 100644 --- a/src/reducer/admin/resource/list/ids.js +++ b/src/reducer/admin/resource/list/ids.js @@ -1,7 +1,7 @@ import { CRUD_GET_LIST_SUCCESS, CRUD_DELETE_SUCCESS, -} from '../../../actions/dataActions'; +} from '../../../../actions/dataActions'; export default resource => ( previousState = [], diff --git a/src/reducer/admin/resource/list/params.js b/src/reducer/admin/resource/list/params.js index 295f122d..36f0eaa9 100644 --- a/src/reducer/admin/resource/list/params.js +++ b/src/reducer/admin/resource/list/params.js @@ -1,4 +1,4 @@ -import { CRUD_CHANGE_LIST_PARAMS } from '../../../actions/listActions'; +import { CRUD_CHANGE_LIST_PARAMS } from '../../../../actions/listActions'; const defaultState = { sort: null, diff --git a/src/reducer/admin/resource/list/total.js b/src/reducer/admin/resource/list/total.js index 571dd5c7..d56dc4e1 100644 --- a/src/reducer/admin/resource/list/total.js +++ b/src/reducer/admin/resource/list/total.js @@ -1,4 +1,4 @@ -import { CRUD_GET_LIST_SUCCESS } from '../../../actions/dataActions'; +import { CRUD_GET_LIST_SUCCESS } from '../../../../actions/dataActions'; export default resource => (previousState = 0, { type, payload, meta }) => { if (!meta || meta.resource !== resource) { diff --git a/src/reducer/admin/saving.js b/src/reducer/admin/saving.js index af238247..73efb5eb 100644 --- a/src/reducer/admin/saving.js +++ b/src/reducer/admin/saving.js @@ -7,7 +7,7 @@ import { CRUD_UPDATE, CRUD_UPDATE_SUCCESS, CRUD_UPDATE_FAILURE, -} from '../actions'; +} from '../../actions'; export default (previousState = false, { type, payload }) => { switch (type) { diff --git a/src/reducer/admin/ui.js b/src/reducer/admin/ui.js index bce4644e..6dc0f45b 100644 --- a/src/reducer/admin/ui.js +++ b/src/reducer/admin/ui.js @@ -1,4 +1,4 @@ -import { TOGGLE_SIDEBAR, SET_SIDEBAR_VISIBILITY } from '../actions'; +import { TOGGLE_SIDEBAR, SET_SIDEBAR_VISIBILITY } from '../../actions'; const defaultState = { sidebarOpen: false, diff --git a/src/reducer/admin/ui.spec.js b/src/reducer/admin/ui.spec.js index e997cc12..298bca6b 100644 --- a/src/reducer/admin/ui.spec.js +++ b/src/reducer/admin/ui.spec.js @@ -1,5 +1,5 @@ import assert from 'assert'; -import { toggleSidebar, setSidebarVisibility } from '../actions/uiActions'; +import { toggleSidebar, setSidebarVisibility } from '../../actions/uiActions'; import reducer from './ui'; describe('ui reducer', () => { diff --git a/src/reducer/index.js b/src/reducer/index.js new file mode 100644 index 00000000..451b6136 --- /dev/null +++ b/src/reducer/index.js @@ -0,0 +1,14 @@ +import { combineReducers } from 'redux'; +import { reducer as formReducer } from 'redux-form'; +import { routerReducer } from 'react-router-redux'; +import adminReducer from './admin'; +import localeReducer from './locale'; + +export default (resources, customReducers, locale) => + combineReducers({ + admin: adminReducer(resources), + locale: localeReducer(locale), + form: formReducer, + routing: routerReducer, + ...customReducers, + }); From e4711d140c770f06ec5104701a98c4c98332b073 Mon Sep 17 00:00:00 2001 From: Francois Zaninotto Date: Tue, 4 Jul 2017 16:39:38 +0200 Subject: [PATCH 09/65] Add NotFound route --- src/AdminRoutes.js | 2 + src/i18n/messages.js | 4 ++ src/mui/layout/NotFound.js | 75 ++++++++++++++++++++++++++++++++++++++ src/mui/layout/index.js | 1 + 4 files changed, 82 insertions(+) create mode 100644 src/mui/layout/NotFound.js diff --git a/src/AdminRoutes.js b/src/AdminRoutes.js index 0e2ab232..292d5696 100644 --- a/src/AdminRoutes.js +++ b/src/AdminRoutes.js @@ -2,6 +2,7 @@ import React from 'react'; import { Redirect, Route, Switch } from 'react-router-dom'; import CrudRoute from './CrudRoute'; +import NotFound from './mui/layout/NotFound'; import Restricted from './auth/Restricted'; const AdminRoutes = ({ customRoutes, resources = [], dashboard }) => @@ -51,6 +52,7 @@ const AdminRoutes = ({ customRoutes, resources = [], dashboard }) => path="/" render={() => } />} + ; export default AdminRoutes; diff --git a/src/i18n/messages.js b/src/i18n/messages.js index 44d23ef2..0ba97866 100644 --- a/src/i18n/messages.js +++ b/src/i18n/messages.js @@ -11,6 +11,7 @@ export default { refresh: 'Refresh', add_filter: 'Add filter', remove_filter: 'Remove this filter', + back: 'Go Back', }, boolean: { true: 'Yes', @@ -23,6 +24,7 @@ export default { create: 'Create %{name}', delete: 'Delete %{name} #%{id}', dashboard: 'Dashboard', + not_found: 'Not Found', }, input: { file: { @@ -42,6 +44,8 @@ export default { no: 'No', are_you_sure: 'Are you sure ?', about: 'About', + not_found: + 'Either you typed a wrong URL, or you followed a bad link', }, navigation: { no_results: 'No results found', diff --git a/src/mui/layout/NotFound.js b/src/mui/layout/NotFound.js new file mode 100644 index 00000000..0e808427 --- /dev/null +++ b/src/mui/layout/NotFound.js @@ -0,0 +1,75 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +import RaisedButton from 'material-ui/RaisedButton'; +import HotTub from 'material-ui/svg-icons/places/hot-tub'; +import History from 'material-ui/svg-icons/action/history'; +import withWidth from 'material-ui/utils/withWidth'; +import compose from 'recompose/compose'; + +import AppBarMobile from './AppBarMobile'; +import translate from '../../i18n/translate'; + +const styles = { + container: { + display: 'flex', + height: '100%', + flexDirection: 'column', + justifyContent: 'center', + }, + containerMobile: { + display: 'flex', + height: '100vh', + flexDirection: 'column', + justifyContent: 'center', + marginTop: '-3em', + }, + icon: { + width: '9em', + height: '9em', + }, + message: { + textAlign: 'center', + fontFamily: 'Roboto, sans-serif', + opacity: 0.5, + margin: '0 1em', + }, + toolbar: { + textAlign: 'center', + marginTop: '1em', + }, +}; + +function goBack() { + history.go(-1); +} + +const NotFound = ({ width, translate }) => +
+ {width === 1 && } +
+ +

+ {translate('aor.page.not_found')} +

+
+ {translate('aor.message.not_found')}. +
+
+
+ } + onClick={goBack} + /> +
+
; + +NotFound.propTypes = { + width: PropTypes.number, + translate: PropTypes.func.isRequired, +}; + +const enhance = compose(withWidth(), translate); + +export default enhance(NotFound); diff --git a/src/mui/layout/index.js b/src/mui/layout/index.js index 3d16bb73..5c46901a 100644 --- a/src/mui/layout/index.js +++ b/src/mui/layout/index.js @@ -4,6 +4,7 @@ export DashboardMenuItem from './DashboardMenuItem'; export Layout from './Layout'; export Menu from './Menu'; export MenuItemLink from './MenuItemLink'; +export NotFound from './NotFound'; export Notification from './Notification'; export Responsive from './Responsive'; export Sidebar from './Sidebar'; From ed788735f8a0e3fde2594b51cdc151a76b7522dd Mon Sep 17 00:00:00 2001 From: Francois Zaninotto Date: Tue, 4 Jul 2017 17:24:23 +0200 Subject: [PATCH 10/65] Add ability to customize NotFound page --- .eslintrc | 8 +------ docs/Admin.md | 42 ++++++++++++++++++++++++++++++++++--- docs/_layouts/default.html | 11 ++++++---- docs/img/not-found.png | Bin 0 -> 37280 bytes src/Admin.js | 4 ++++ src/AdminRoutes.js | 17 +++++++++++++-- src/mui/layout/Layout.js | 10 ++++++++- 7 files changed, 75 insertions(+), 17 deletions(-) create mode 100644 docs/img/not-found.png diff --git a/.eslintrc b/.eslintrc index 8b38207c..88cf4fe4 100644 --- a/.eslintrc +++ b/.eslintrc @@ -25,13 +25,7 @@ } ], "react/forbid-prop-types": [ - "warn", - { - "forbid": [ - "any", - "array" - ] - } + "off" ], "react/prop-types": [ "warn" diff --git a/docs/Admin.md b/docs/Admin.md index 6e70a918..458b45b3 100644 --- a/docs/Admin.md +++ b/docs/Admin.md @@ -29,6 +29,7 @@ Here are all the props accepted by the component: * [`restClient`](#restclient) * [`title`](#title) * [`dashboard`](#dashboard) +* [`notFound](#notfound) * [`menu`](#menu) * [`theme`](#theme) * [`appLayout`](#applayout) @@ -81,7 +82,6 @@ const App = () => ( By default, the homepage of an an admin app is the `list` of the first child ``. But you can also specify a custom component instead. To fit in the general design, use Material UI's `` component, and admin-on-rest's `` component: -{% raw %} ```jsx // in src/Dashboard.js import React from 'react'; @@ -95,7 +95,6 @@ export default () => ( ); ``` -{% endraw %} ```jsx // in src/App.js @@ -110,6 +109,43 @@ const App = () => ( ![Custom home page](http://static.marmelab.com/admin-on-rest/dashboard.png) +## `notFound` + +When users type URLs that don't match any of the children `` components, they see a default "Not Found" page. + +![Not Found](./img/not-found.png) + +You can customize this page to use the component of your choice by passing it as the `notFound` prop. To fit in the general design, use Material UI's `` component, and admin-on-rest's `` component: + +```jsx +// in src/NotFound.js +import React from 'react'; +import { Card, CardText } from 'material-ui/Card'; +import { ViewTitle } from 'admin-on-rest/lib/mui'; + +export default () => ( + + + +

404: Page not found

+
+
+); +``` + +```jsx +// in src/App.js +import NotFound from './NotFound'; + +const App = () => ( + + // ... + +); +``` + +**Tip**: If your custom `notFound` component contains react-router `` components, this allows you to register new routes displayed within the admin-on-rest layout easily. Note that these routes will match *after* all the admin-on-rest resource routes have been tested. To add custom routes *before* the admin-on-rest ones, and therefore override the default resource routes, use the [`customRoutes` prop](#customroutes) instead. + ## `menu` Admin-on-rest uses the list of `` components passed as children of `` to build a menu to each resource with a `list` component. @@ -314,7 +350,7 @@ Now, when a user browses to `/foo` or `/bar`, the components you defined will ap **Tip**: It's up to you to create a [custom menu](#applayout) entry, or custom buttons, to lead to your custom pages. -**Tip**: Your custom pages take precedence over admin-on-rest's own routes. That means that `customRoutes` lets you override any route you want! +**Tip**: Your custom pages take precedence over admin-on-rest's own routes. That means that `customRoutes` lets you override any route you want! If you want to add routes *after* all the admin-on-rest routes, use the [`notFound` prop](#notfound) instead. **Tip**: To look like other admin-on-rest pages, your custom pages should have the following structure: diff --git a/docs/_layouts/default.html b/docs/_layouts/default.html index 1890775a..17962f36 100644 --- a/docs/_layouts/default.html +++ b/docs/_layouts/default.html @@ -141,6 +141,9 @@
  • messages
  • +
  • + notFound +
  • restClient
  • @@ -533,10 +536,10 @@