diff --git a/__tests__/end-to-end.js b/__tests__/end-to-end.js index 58fe6cacc..aa340805e 100644 --- a/__tests__/end-to-end.js +++ b/__tests__/end-to-end.js @@ -564,6 +564,17 @@ async function pickColor (containerSelector: string, color: string) { await clearAndType(`${containerSelector} input`, color) } +/** + * A helper method to choose a route type + * in the route editor (but not in the feed editor). + */ +async function pickRouteType (containerSelector: string, routeOptionId: string) { + await click(`${containerSelector} a`) + await waitForSelector(`${containerSelector} .dropdown-content`) + await waitForSelector(`[data-test-id="${routeOptionId}"]`) + await click(`[data-test-id="${routeOptionId}"] label`) +} + /** * A helper method to type in an autocomplete value and then select an option * from an react-select component. @@ -1599,9 +1610,9 @@ describe('end-to-end', () => { ) // route type - await page.select( - '[data-test-id="route-route_type-input-container"] select', - '3' + await pickRouteType( + '[data-test-id="route-route_type-input-container"]', + 'route-type-option-3' ) // route color diff --git a/gtfs.yml b/gtfs.yml index 0674ed8f1..d23c2126e 100644 --- a/gtfs.yml +++ b/gtfs.yml @@ -297,7 +297,7 @@ helpContent: 'Contains a description of a route. Please provide useful, quality information. Do not simply duplicate the name of the route. For example, "A trains operate between Inwood-207 St, Manhattan and Far Rockaway-Mott Avenue, Queens at all times. Also from about 6AM until about midnight, additional A trains operate between Inwood-207 St and Lefferts Boulevard (trains typically alternate between Lefferts Blvd and Far Rockaway)."' - name: route_type required: true - inputType: DROPDOWN + inputType: GTFS_ROUTE_TYPE bulkEditEnabled: true options: - value: 3 @@ -316,7 +316,157 @@ text: Gondola - value: 7 text: Funicular - columnWidth: 6 + - value: 11 + text: Trolleybus + - value: 12 + text: Monorail + - value: 100 + text: Railway Service + - value: 101 + text: High Speed Rail Service + - value: 102 + text: Long Distance Trains + - value: 103 + text: Inter Regional Rail Service + - value: 104 + text: Car Transport Rail Service + - value: 105 + text: Sleeper Rail Service + - value: 106 + text: Regional Rail Service + - value: 107 + text: Tourist Railway Service + - value: 108 + text: Rail Shuttle (Within Complex) + - value: 109 + text: Suburban Railway + - value: 110 + text: Replacement Rail Service + - value: 111 + text: Special Rail Service + - value: 112 + text: Lorry Transport Rail Service + - value: 113 + text: All Rail Services + - value: 114 + text: Cross-Country Rail Service + - value: 115 + text: Vehicle Transport Rail Service + - value: 116 + text: Rack and Pinion Railway + - value: 117 + text: Additional Rail Service + - value: 200 + text: Coach Service + - value: 201 + text: International Coach Service + - value: 202 + text: National Coach Service + - value: 203 + text: Shuttle Coach Service + - value: 204 + text: Regional Coach Service + - value: 205 + text: Special Coach Service + - value: 206 + text: Sightseeing Coach Service + - value: 207 + text: Tourist Coach Service + - value: 208 + text: Commuter Coach Service + - value: 209 + text: All Coach Services + - value: 400 + text: Urban Railway Service + - value: 401 + text: Metro Service + - value: 402 + text: Underground Service + - value: 403 + text: Urban Railway Service + - value: 404 + text: All Urban Railway Services + - value: 405 + text: Monorail + - value: 700 + text: Bus Service + - value: 701 + text: Regional Bus Service + - value: 702 + text: Express Bus Service + - value: 703 + text: Stopping Bus Service + - value: 704 + text: Local Bus Service + - value: 705 + text: Night Bus Service + - value: 706 + text: Post Bus Service + - value: 707 + text: Special Needs Bus + - value: 708 + text: Mobility Bus Service + - value: 709 + text: Mobility Bus for Registered Disabled + - value: 710 + text: Sightseeing Bus + - value: 711 + text: Shuttle Bus + - value: 712 + text: School Bus + - value: 713 + text: School and Public Service Bus + - value: 714 + text: Rail Replacement Bus Service + - value: 715 + text: Demand and Response Bus Service + - value: 716 + text: All Bus Services + - value: 900 + text: Tram Service + - value: 901 + text: City Tram Service + - value: 902 + text: Local Tram Service + - value: 903 + text: Regional Tram Service + - value: 904 + text: Sightseeing Tram Service + - value: 905 + text: Shuttle Tram Service + - value: 906 + text: All Tram Services + - value: 1000 + text: Water Transport Service + - value: 1100 + text: Air Service + - value: 1200 + text: Ferry Service + - value: 1300 + text: Aerial Lift Service + - value: 1400 + text: Funicular Service + - value: 1500 + text: Taxi Service + - value: 1501 + text: Communal Taxi Service + - value: 1502 + text: Water Taxi Service + - value: 1503 + text: Rail Taxi Service + - value: 1504 + text: Bike Taxi Service + - value: 1505 + text: Licensed Taxi Service + - value: 1506 + text: Private Hire Service Vehicle + - value: 1507 + text: All Taxi Services + - value: 1700 + text: Miscellaneous Service + - value: 1702 + text: Horse-drawn Carriage + columnWidth: 12 helpContent: The route_type field describes the type of transportation used on a route. Valid values for this field are... - name: route_sort_order required: false diff --git a/lib/editor/components/EditorInput.js b/lib/editor/components/EditorInput.js index 5b291fabf..e1083b3a1 100644 --- a/lib/editor/components/EditorInput.js +++ b/lib/editor/components/EditorInput.js @@ -1,27 +1,28 @@ // @flow -import React, {Component} from 'react' -import {Checkbox, FormControl, FormGroup, ControlLabel, Tooltip, OverlayTrigger} from 'react-bootstrap' -import Select from 'react-select' import moment from 'moment' +import * as React from 'react' +import {Checkbox, FormControl, FormGroup, ControlLabel, Tooltip, OverlayTrigger} from 'react-bootstrap' import DateTimeField from 'react-bootstrap-datetimepicker' import Dropzone from 'react-dropzone' +import Select from 'react-select' import * as activeActions from '../actions/active' import * as editorActions from '../actions/editor' -import ColorField from './ColorField' import {getEntityName, getTableById} from '../util/gtfs' import {FIELD_PROPS} from '../util/types' import {doesNotExist} from '../util/validation' -import VirtualizedEntitySelect from './VirtualizedEntitySelect' import TimezoneSelect from '../../common/components/TimezoneSelect' import LanguageSelect from '../../common/components/LanguageSelect' import toSentenceCase from '../../common/util/to-sentence-case' -import ZoneSelect from './ZoneSelect' - import type {Entity, Feed, GtfsSpecField, GtfsAgency, GtfsStop} from '../../types' import type {EditorTables} from '../../types/reducers' +import ColorField from './ColorField' +import RouteTypeSelect from './RouteTypeSelect' +import VirtualizedEntitySelect from './VirtualizedEntitySelect' +import ZoneSelect from './ZoneSelect' + type Props = { activeComponent: string, activeEntity?: Entity, @@ -53,10 +54,18 @@ const entityToOption = (entity: ?Entity, key: string) => { : null } -export default class EditorInput extends Component { - _onColorChange = (color: any) => { - const {onChange, updateActiveGtfsEntity, activeEntity, activeComponent, field} = this.props - const val = color.hex.split('#')[1] +export default class EditorInput extends React.Component { + /** + * Helper method for processing field value changes. + */ + _processFieldChange = (val: any) => { + const { + activeComponent, + activeEntity, + field, + onChange, + updateActiveGtfsEntity + } = this.props onChange && onChange(val) updateActiveGtfsEntity && activeEntity && updateActiveGtfsEntity({ component: activeComponent, @@ -65,48 +74,28 @@ export default class EditorInput extends Component { }) } + _onColorChange = (color: any) => { + this._processFieldChange(color.hex.split('#')[1]) + } + _onDateChange = (millis: number) => { - const {onChange, updateActiveGtfsEntity, activeEntity, activeComponent, field} = this.props - const val = moment(+millis).format('YYYYMMDD') - onChange && onChange(val) - updateActiveGtfsEntity && activeEntity && updateActiveGtfsEntity({ - component: activeComponent, - entity: activeEntity, - props: {[field.name]: val} - }) + this._processFieldChange(moment(+millis).format('YYYYMMDD')) } _onDowChange = (evt: SyntheticInputEvent) => { - const {onChange, updateActiveGtfsEntity, activeEntity, activeComponent, field} = this.props - const val = evt.target.checked ? 1 : 0 - onChange && onChange(val) - updateActiveGtfsEntity && activeEntity && updateActiveGtfsEntity({ - component: activeComponent, - entity: activeEntity, - props: {[field.name]: val} - }) + this._processFieldChange(evt.target.checked ? 1 : 0) } _onInputChange = (evt: SyntheticInputEvent) => { - const {onChange, updateActiveGtfsEntity, activeEntity, activeComponent, field} = this.props - const val = evt.target.value - onChange && onChange(val) - updateActiveGtfsEntity && activeEntity && updateActiveGtfsEntity({ - component: activeComponent, - entity: activeEntity, - props: {[field.name]: val} - }) + this._processFieldChange(evt.target.value) + } + + _onRouteTypeChange = (currentNode: any) => { + this._processFieldChange(currentNode.value) } _onSelectChange = (option: any) => { - const {onChange, updateActiveGtfsEntity, activeEntity, activeComponent, field} = this.props - const val = option ? option.value : null - onChange && onChange(val) - updateActiveGtfsEntity && activeEntity && updateActiveGtfsEntity({ - component: activeComponent, - entity: activeEntity, - props: {[field.name]: val} - }) + this._processFieldChange(option ? option.value : null) } _uploadBranding = (files: Array) => { @@ -300,6 +289,7 @@ export default class EditorInput extends Component { ) case 'DROPDOWN': + const options = field.options || [] return ( { {basicLabel} + disabled={approveGtfsDisabled && field.adminOnly} + // set value to '' to allow for selection of disabled option + value={!doesNotExist(value) ? value : ''} + > {/* Add field for empty string value if that is not an allowable option so that user selection triggers onChange */} - {field.options && field.options.findIndex(option => option.value === '') === -1 - ? - : null - } - {field.options && field.options.map(o => ())} + {options.findIndex(option => option.value === '') === -1 && ( + + )} + {field.options && field.options.map(o => ( + + ))} ) @@ -331,6 +326,18 @@ export default class EditorInput extends Component { onChange={this._onSelectChange} /> ) } + case 'GTFS_ROUTE_TYPE': { + return ( + + {basicLabel} + + + ) + } case 'GTFS_AGENCY': const agencies = getTableById(tableData, 'agency') const agency = agencies.find(a => a.agency_id === currentValue) diff --git a/lib/editor/components/RouteTypeSelect.js b/lib/editor/components/RouteTypeSelect.js new file mode 100644 index 000000000..b9d1fbf8d --- /dev/null +++ b/lib/editor/components/RouteTypeSelect.js @@ -0,0 +1,133 @@ +// @flow + +import React, { Component } from 'react' +import DropdownTreeSelect from 'react-dropdown-tree-select' + +import type { GtfsSpecField } from '../../types' + +type Props = { + field: GtfsSpecField, + onRouteTypeChange: (any, any) => void, + routeType: number +} + +type State = { + data: any +} + +type NumericalOption = { + disabled?: boolean, + text: string, + value: number +} + +function convertValueToNumber ({ text, value }: { text: string, value: string }): NumericalOption { + return { + text, + value: parseInt(value, 10) + } +} +/** + * Determines whether a route is standard or not. + */ +function isStandardRouteType ({ value }: { value: number }) { + return value < 100 +} + +/** + * Creates the route types tree structure for the route type selector. + */ +function getRouteTypeOptions (field: GtfsSpecField, routeType: number) { + // Convert option values into numbers. + const routeTypes = (field.options || []).map(convertValueToNumber) + + const standardRouteTypes = routeTypes.filter(isStandardRouteType) + const extendedRouteTypes = routeTypes.filter(opt => !isStandardRouteType(opt)) + // Show unknown value as invalid and prevent user from picking that value. + const unknownRouteTypes = [] + if (!routeTypes.find(opt => opt.value === routeType)) { + unknownRouteTypes.push({ + disabled: true, + text: 'Invalid', + value: routeType + }) + } + + // Helper function that converts a field option to an entry for the tree selector. + // It is inline because it uses the routeType argument. + const toTreeOption = ({ disabled, text, value }: NumericalOption) => ({ + checked: value === routeType, + disabled, + label: `${text} (${value})`, + value + }) + + // Variant of the function above for standard route types + // that includes a data-test-id for e2e tests. + const toTreeOptionWithDataId = (opt: NumericalOption) => ({ + ...toTreeOption(opt), + dataset: { testId: `route-type-option-${opt.value}` } + }) + + // Display in this order: + // - non-standard/unknown route type used for this route, if applicable, + // - standard route types + // - extended route types, if configured. + return [ + ...unknownRouteTypes.map(toTreeOption), + ...standardRouteTypes.map(toTreeOptionWithDataId), + ...(extendedRouteTypes.length > 0 ? [{ + children: [ + // Group children by category (e.g. all 101-199 route types fall under 100-Railway). + // Get all the categories here. + ...extendedRouteTypes + .filter(opt => opt.value % 100 === 0) + .map(category => ({ + ...toTreeOption(category), + // Add the children for each category. + children: extendedRouteTypes + .filter( + opt => opt.value > category.value && opt.value < category.value + 100 + ) + .map(toTreeOption) + })) + ], + // Used for CSS no-pointer and font effects. + className: 'extended-values-node', + label: 'Extended GTFS Route Types' + }] : []) + ] +} + +/** + * Encapsulates a drop-down selector with a hierarchical (tree) list of choices, + * and filters out unnecessary prop changes to prevent flicker. + */ +export default class RouteTypeSelect extends Component { + constructor (props: Props) { + super(props) + this.state = { data: getRouteTypeOptions(props.field, props.routeType) } + } + + /** + * Prevent resetting of any expanded status of the tree hierarchy + * (datatools polls the backend every 10 seconds and that causes new props + * to be passed to the editor component). + */ + shouldComponentUpdate (nextProps: Props) { + return nextProps.routeType !== this.props.routeType && + nextProps.field !== this.props.field + } + + render () { + return ( + + ) + } +} diff --git a/lib/editor/util/__tests__/validation.js b/lib/editor/util/__tests__/validation.js index 083a454c3..6bb866167 100644 --- a/lib/editor/util/__tests__/validation.js +++ b/lib/editor/util/__tests__/validation.js @@ -10,6 +10,17 @@ const routeSortOrderField = { name: 'route_sort_order', required: false } +const routeTypeField = { + columnWidth: 12, + inputType: 'GTFS_ROUTE_TYPE', + name: 'route_type', + options: [ + { text: '0', value: '0' }, + { text: '3', value: '3' }, + { text: '101', value: '101' } + ], + required: true +} const shapedDistTraveledField = { columnWidth: 6, inputType: 'POSITIVE_NUM', @@ -132,5 +143,35 @@ describe('editor > util > gtfs >', () => { ).toEqual(false) }) }) + + describe('GTFS_ROUTE_TYPE', () => { + it('36 should be invalid', () => { + expect( + validate(routeTypeField, '36', null, null, defaultTablesData) + ).toEqual({ + field: 'route_type', + invalid: true, + reason: 'Field must be a valid route type' + }) + }) + + it('0 (tram) should be valid', () => { + expect( + validate(routeTypeField, '0', null, null, defaultTablesData) + ).toEqual(false) + }) + + it('3 (bus) should be valid', () => { + expect( + validate(routeTypeField, '3', null, null, defaultTablesData) + ).toEqual(false) + }) + + it('101 (High Speed Rail) should be valid', () => { + expect( + validate(routeTypeField, '101', null, null, defaultTablesData) + ).toEqual(false) + }) + }) }) }) diff --git a/lib/editor/util/validation.js b/lib/editor/util/validation.js index d6ddd7601..a0525d0eb 100644 --- a/lib/editor/util/validation.js +++ b/lib/editor/util/validation.js @@ -4,11 +4,11 @@ import clone from 'lodash/cloneDeep' import moment from 'moment' import validator from 'validator' -import {getTableById} from './gtfs' - import type {Entity, GtfsSpecField, ScheduleException} from '../../types' import type {EditorTables} from '../../types/reducers' +import {getTableById} from './gtfs' + export type EditorValidationIssue = { field: string, invalid: boolean, @@ -101,6 +101,33 @@ export function validate ( } } + /** + * Checks whether value is a positive number + */ + function checkGtfsRouteType (): CheckPositiveOutput { + // Route type should meet required/optional criteria and be positive. + const positiveNumberCheck = checkPositiveNumber() + const { num, result } = positiveNumberCheck + + if (result !== false) return positiveNumberCheck + + // Make sure positive number value is one of those defined for route type options. + // (force string comparison to avoid number/string ambiguities) + const isValid = num !== undefined && num !== null && field.options && field.options.find( + opt => opt.value.toString() === num.toString() + ) + if (!isValid) { + return { + result: validationIssue('Field must be a valid route type') + } + } + + return { + num, + result: false + } + } + switch (inputType) { case 'GTFS_ID': // Indices contains list of all indexes of occurrences of the ID value. @@ -337,6 +364,8 @@ export function validate ( return false case 'POSITIVE_NUM': return checkPositiveNumber().result + case 'GTFS_ROUTE_TYPE': + return checkGtfsRouteType().result case 'GTFS_ROUTE': case 'GTFS_STOP': case 'DATE': diff --git a/lib/index.css b/lib/index.css index f7ae2991e..0fd25073a 100644 --- a/lib/index.css +++ b/lib/index.css @@ -17,5 +17,6 @@ @import url(node_modules/rc-slider/assets/index.css); @import url(node_modules/react-toggle/style.css); @import url(node_modules/react-toastify/dist/ReactToastify.min.css); +@import url(node_modules/react-dropdown-tree-select/dist/styles.css); @import url(lib/style.css); diff --git a/lib/style.css b/lib/style.css index 543611b83..9bfd1a0e1 100644 --- a/lib/style.css +++ b/lib/style.css @@ -879,3 +879,120 @@ h4.line:after { .recent-activity-inner { margin-left: 36; } + + +/* Custom classes for the DropdownTreeSelect component that + * override CSS from 'react-dropdown-tree-select/dist/styles.css'. + * Modified from https://dowjones.github.io/react-dropdown-tree-select/#/story/with-bootstrap-styles. + * !important tags are used because somehow the tree selector default styles are applied after this stylesheet. + */ + +.route-type-select .dropdown { + display: block; +} + + /* Make some styles look like bootstrap. */ +.route-type-select .dropdown-trigger { + background-color: #fff; + border-color: #ccc!important; + border-radius: 4px; + color: #555; + padding: 4px 6px!important; + width: 100%; +} + +/* Remove border and background of tags + * (a "tag" is a selected item displayed inside the input portion of the selector). */ +.route-type-select .dropdown-trigger .tag { + background: none!important; + border: none!important; +} + +/* Hide the search box and the mini "delete" button next to the value. */ +.route-type-select .dropdown-trigger .tag-item, +.route-type-select .dropdown-trigger .tag-item .search, +.route-type-select .dropdown-trigger .tag-remove { + display: none; +} + +.route-type-select .dropdown-trigger .tag-item:first-of-type { + display: inherit; +} + +/* Position the drop-down arrow. */ +.route-type-select .dropdown-trigger.arrow:after { + border-style: solid; + height: 0; + position: absolute; + right: 8px; + top: 50%; + width: 0; +} +.route-type-select .dropdown-trigger.arrow.bottom:after { + border-color: #999 transparent transparent; + border-width: 5px 5px 2.5px; + content: ''!important; + margin-top: -5px; +} +.route-type-select .dropdown-trigger.arrow.top:after { + border-color: transparent transparent #999; + border-width: 2.5px 5px 5px; + content: ''!important; + margin-top: -7.5px; +} + +/* Adjust position and width of the dropdown content wrt the input itself. */ +.route-type-select .dropdown-content { + margin-top: -4px; + width: 100%; +} + +/* Hide the radio boxes in the tree list. */ +.route-type-select .dropdown-content input[type=radio] { + display: none; +} + +/* Highlight the selected item, using same color as react-select 1.x. */ +.route-type-select .dropdown-content .node.checked { + background-color: #f5faff; +} +.route-type-select .dropdown-content .node:focus, +.route-type-select .dropdown-content .node:hover { + background-color: #ebf5ff; +} + +/* Don't show disabled itens (e.g. because they represent invalid values) */ +.route-type-select .dropdown-content .node.disabled { + display: none; +} + +/* Reduce v-space between tree items, adjust font color. */ +.route-type-select .dropdown-content .node { + color: #555; + padding: 2px; +} + +.route-type-select .dropdown-content .node > label { + /* Use regular font weight on labels rendered by the tree dropdown. */ + font-weight: normal; + /* Make labels clickable on their entire width on their right... */ + width: 100%; +} + +/* ... but disable clicks for the header (not the +/- sign) for the extended routes. */ +.route-type-select .dropdown-content .node.tree.extended-values-node > label { + color: #999; + pointer-events: none; +} + +/* For the plus/minus symbols for the tree view. */ +.route-type-select .toggle { + color: #555; + font: normal normal normal 12px/1 FontAwesome; +} +.route-type-select .toggle.collapsed::after { + content: "\f067"; +} +.route-type-select .toggle.expanded::after { + content: "\f068"; +} diff --git a/lib/types/index.js b/lib/types/index.js index 6b8860296..c71a8f256 100644 --- a/lib/types/index.js +++ b/lib/types/index.js @@ -26,6 +26,7 @@ type InputType = 'GTFS_AGENCY' | 'POSITIVE_INT' | 'GTFS_ROUTE' | + 'GTFS_ROUTE_TYPE' | 'GTFS_SERVICE' | 'GTFS_BLOCK' | 'GTFS_SHAPE' | diff --git a/package.json b/package.json index ae774542e..e1bb63686 100644 --- a/package.json +++ b/package.json @@ -79,6 +79,7 @@ "react-dnd": "^2.1.4", "react-dnd-html5-backend": "^2.1.2", "react-dom": "^15.4.1", + "react-dropdown-tree-select": "^2.5.1", "react-dropzone": "^3.5.3", "react-ga": "^2.3.5", "react-leaflet": "1.1.0", diff --git a/yarn.lock b/yarn.lock index 486c63a5b..46ad6abda 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1892,6 +1892,11 @@ array-unique@^0.3.2: resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" integrity sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg= +array.partial@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/array.partial/-/array.partial-1.0.5.tgz#f514595aacbf6a99ea20bbcffbcacc10316f0d56" + integrity sha512-nkHH1dU6JXrwppCqdUD5M1R85vihgBqhk9miq+3WFwwRayNY1ggpOT6l99PppqYQ1Hcjv2amFfUzhe25eAcYfA== + array.prototype.find@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/array.prototype.find/-/array.prototype.find-2.0.0.tgz#56a9ab1edde2a7701ed6d9166acec338919d8430" @@ -11766,6 +11771,14 @@ react-dom@^15.4.1, react-dom@^15.6.2: object-assign "^4.1.0" prop-types "^15.5.10" +react-dropdown-tree-select@^2.5.1: + version "2.5.1" + resolved "https://registry.yarnpkg.com/react-dropdown-tree-select/-/react-dropdown-tree-select-2.5.1.tgz#071bef6193352cda8f3177d7f56e906a8fa175e3" + integrity sha512-9sN62pIci6EZ0Fni5fqB7b5e7UG/FQ/qSwYtcc0+P3VaE1bHnEYQ9XxTSkFqpbVsy9dfq94RGaECzWcErgB+jw== + dependencies: + array.partial "^1.0.5" + react-infinite-scroll-component "^4.0.2" + react-dropzone@^3.5.3: version "3.13.4" resolved "https://registry.yarnpkg.com/react-dropzone/-/react-dropzone-3.13.4.tgz#84da26815c40339691c49b4544c2ef7a16912ccc" @@ -11779,6 +11792,11 @@ react-ga@^2.3.5: resolved "https://registry.yarnpkg.com/react-ga/-/react-ga-2.5.6.tgz#5a2e2fa78ae298e5b0a4498210eeec631ef1b562" integrity sha512-g04dz6zrbdHRxVaURPWT3RUbjLflh74sS6dCuhGeZupj7ii+UEt9lwTjALb2ST2w+7wAmzG1YqYlNX4yvRXe1g== +react-infinite-scroll-component@^4.0.2: + version "4.5.3" + resolved "https://registry.yarnpkg.com/react-infinite-scroll-component/-/react-infinite-scroll-component-4.5.3.tgz#008c2ec358628b490117ffc4aa6ce6982b26f8be" + integrity sha512-8O0PIeYZx0xFVS1ChLlLl/1obn64vylzXeheLsm+t0qUibmet7U6kDaKFg6jVRQJwDikWBTcyqEFFsxrbFCO5w== + react-input-autosize@^2.1.2: version "2.2.1" resolved "https://registry.yarnpkg.com/react-input-autosize/-/react-input-autosize-2.2.1.tgz#ec428fa15b1592994fb5f9aa15bb1eb6baf420f8"