mirror of
				https://github.com/strapi/strapi.git
				synced 2025-10-31 09:56:44 +00:00 
			
		
		
		
	Update SelectOne and SelectMany
This commit is contained in:
		
							parent
							
								
									1157f6ae75
								
							
						
					
					
						commit
						21177a4670
					
				| @ -1,267 +1,69 @@ | ||||
| /** | ||||
|  * | ||||
|  * SelectMany | ||||
|  * | ||||
|  */ | ||||
| 
 | ||||
| /* eslint-disable */ | ||||
| import React from 'react'; | ||||
| import Select from 'react-select'; | ||||
| import { FormattedMessage } from 'react-intl'; | ||||
| import PropTypes from 'prop-types'; | ||||
| import { | ||||
|   cloneDeep, | ||||
|   includes, | ||||
|   isArray, | ||||
|   isNull, | ||||
|   isUndefined, | ||||
|   get, | ||||
|   findIndex, | ||||
|   isEmpty, | ||||
| } from 'lodash'; | ||||
| import { get, isEmpty } from 'lodash'; | ||||
| 
 | ||||
| // Utils.
 | ||||
| import { request, templateObject } from 'strapi-helper-plugin'; | ||||
| import Select from 'react-select'; | ||||
| 
 | ||||
| // Component.
 | ||||
| import SortableList from './SortableList'; | ||||
| // CSS.
 | ||||
| import styles from './styles.scss'; | ||||
| 
 | ||||
| class SelectMany extends React.PureComponent { | ||||
|   state = { | ||||
|     isLoading: true, | ||||
|     options: [], | ||||
|     start: 0, | ||||
|   }; | ||||
| 
 | ||||
|   componentDidMount() { | ||||
|     this.getOptions(''); | ||||
|   } | ||||
| 
 | ||||
|   componentDidUpdate(prevProps, prevState) { | ||||
|     if (isEmpty(prevProps.record) && !isEmpty(this.props.record)) { | ||||
|       const values = ( | ||||
|         get(this.props.record, this.props.relation.alias) || [] | ||||
|       ).map(el => el.id || el._id); | ||||
| 
 | ||||
|       const options = this.state.options.filter(el => { | ||||
|         return !values.includes(el.value.id || el.value._id); | ||||
|       }); | ||||
| 
 | ||||
|       this.state.options = options; | ||||
|     } | ||||
| 
 | ||||
|     if (prevState.start !== this.state.start) { | ||||
|       this.getOptions(''); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   getOptions = query => { | ||||
|     const params = { | ||||
|       _limit: 20, | ||||
|       _start: this.state.start, | ||||
|       source: this.props.relation.plugin || 'content-manager', | ||||
|     }; | ||||
| 
 | ||||
|     // Set `query` parameter if necessary
 | ||||
|     if (query) { | ||||
|       delete params._limit; | ||||
|       delete params._skip; | ||||
|       params[`${this.props.relation.displayedAttribute}_contains`] = query; | ||||
|     } | ||||
|     // Request URL
 | ||||
|     const requestUrl = `/content-manager/explorer/${this.props.relation.model || | ||||
|       this.props.relation.collection}`;
 | ||||
| 
 | ||||
|     // Call our request helper (see 'utils/request')
 | ||||
|     return request(requestUrl, { | ||||
|       method: 'GET', | ||||
|       params, | ||||
|     }) | ||||
|       .then(response => { | ||||
|         /* eslint-disable indent */ | ||||
|         const options = isArray(response) | ||||
|           ? response.map(item => ({ | ||||
|               value: item, | ||||
|               label: templateObject( | ||||
|                 { mainField: this.props.relation.displayedAttribute }, | ||||
|                 item | ||||
|               ).mainField, | ||||
|             })) | ||||
|           : [ | ||||
|               { | ||||
|                 value: response, | ||||
|                 label: response[this.props.relation.displayedAttribute], | ||||
|               }, | ||||
|             ]; | ||||
|         /* eslint-enable indent */ | ||||
|         const newOptions = cloneDeep(this.state.options); | ||||
|         options.map(option => { | ||||
|           // Don't add the values when searching
 | ||||
|           if ( | ||||
|             findIndex(newOptions, o => o.value.id === option.value.id) === -1 | ||||
|           ) { | ||||
|             return newOptions.push(option); | ||||
|           } | ||||
|         }); | ||||
| 
 | ||||
|         return this.setState({ | ||||
|           options: newOptions, | ||||
|           isLoading: false, | ||||
|         }); | ||||
|       }) | ||||
|       .catch(() => { | ||||
|         strapi.notification.error( | ||||
|           'content-manager.notification.error.relationship.fetch' | ||||
|         ); | ||||
|       }); | ||||
|   }; | ||||
| 
 | ||||
|   handleInputChange = value => { | ||||
|     const clonedOptions = this.state.options; | ||||
|     const filteredValues = clonedOptions.filter(data => | ||||
|       includes(data.label, value) | ||||
|     ); | ||||
| 
 | ||||
|     if (filteredValues.length === 0) { | ||||
|       return this.getOptions(value); | ||||
|     } | ||||
|   }; | ||||
| 
 | ||||
|   handleChange = value => { | ||||
|     // Remove new added value from available option;
 | ||||
|     this.state.options = this.state.options.filter( | ||||
|       el => | ||||
|         !((el.value._id || el.value.id) === (value.value.id || value.value._id)) | ||||
|     ); | ||||
| 
 | ||||
|     this.props.onAddRelationalItem({ | ||||
|       key: this.props.relation.alias, | ||||
|       value: value.value, | ||||
|     }); | ||||
|   }; | ||||
| 
 | ||||
|   handleBottomScroll = () => { | ||||
|     this.setState(prevState => { | ||||
|       return { | ||||
|         start: prevState.start + 1, | ||||
|       }; | ||||
|     }); | ||||
|   }; | ||||
| 
 | ||||
|   handleRemove = index => { | ||||
|     const values = get(this.props.record, this.props.relation.alias); | ||||
| 
 | ||||
|     // Add removed value from available option;
 | ||||
|     const toAdd = { | ||||
|       value: values[index], | ||||
|       label: templateObject( | ||||
|         { mainField: this.props.relation.displayedAttribute }, | ||||
|         values[index] | ||||
|       ).mainField, | ||||
|     }; | ||||
| 
 | ||||
|     this.setState(prevState => ({ | ||||
|       options: prevState.options.concat([toAdd]), | ||||
|     })); | ||||
| 
 | ||||
|     this.props.onRemoveRelationItem({ | ||||
|       key: this.props.relation.alias, | ||||
|       index, | ||||
|     }); | ||||
|   }; | ||||
| 
 | ||||
|   // Redirect to the edit page
 | ||||
|   handleClick = (item = {}) => { | ||||
|     this.props.onRedirect({ | ||||
|       model: this.props.relation.collection || this.props.relation.model, | ||||
|       id: item.value.id || item.value._id, | ||||
|       source: this.props.relation.plugin, | ||||
|     }); | ||||
|   }; | ||||
| 
 | ||||
|   render() { | ||||
|     const description = this.props.relation.description ? ( | ||||
|       <p>{this.props.relation.description}</p> | ||||
|     ) : ( | ||||
|       '' | ||||
|     ); | ||||
|     const value = get(this.props.record, this.props.relation.alias) || []; | ||||
| 
 | ||||
|     /* eslint-disable jsx-a11y/label-has-for */ | ||||
| function SelectMany({ | ||||
|   addRelation, | ||||
|   mainField, | ||||
|   name, | ||||
|   isLoading, | ||||
|   onInputChange, | ||||
|   onMenuClose, | ||||
|   onMenuScrollToBottom, | ||||
|   options, | ||||
|   placeholder, | ||||
|   value, | ||||
| }) { | ||||
|   return ( | ||||
|       <div | ||||
|         className={`form-group ${styles.selectMany} ${value.length > 4 && | ||||
|           styles.selectManyUpdate}`}
 | ||||
|       > | ||||
|         <label htmlFor={this.props.relation.alias}> | ||||
|           {this.props.relation.alias} <span>({value.length})</span> | ||||
|         </label> | ||||
|         {description} | ||||
|     <> | ||||
|       <Select | ||||
|           className={`${styles.select}`} | ||||
|           id={this.props.relation.alias} | ||||
|           isLoading={this.state.isLoading} | ||||
|           onChange={this.handleChange} | ||||
|           onInputChange={this.handleInputChange} | ||||
|           onMenuScrollToBottom={this.handleBottomScroll} | ||||
|           options={this.state.options} | ||||
|           placeholder={ | ||||
|             <FormattedMessage id="content-manager.containers.Edit.addAnItem" /> | ||||
|         id={name} | ||||
|         filterOption={el => { | ||||
|           if (isEmpty(value)) { | ||||
|             return true; | ||||
|           } | ||||
| 
 | ||||
|           return value.findIndex(obj => obj.id === el.value.id) === -1; | ||||
|         }} | ||||
|         isLoading={isLoading} | ||||
|         isMulti | ||||
|         isSearchable | ||||
|         options={options} | ||||
|         onChange={addRelation} | ||||
|         onInputChange={onInputChange} | ||||
|         onMenuClose={onMenuClose} | ||||
|         onMenuScrollToBottom={onMenuScrollToBottom} | ||||
|         placeholder={placeholder} | ||||
|         value={[]} | ||||
|       /> | ||||
|         <SortableList | ||||
|           items={ | ||||
|             /* eslint-disable indent */ | ||||
|             isNull(value) || isUndefined(value) || value.size === 0 | ||||
|               ? null | ||||
|               : value.map(item => { | ||||
|                   if (item) { | ||||
|                     return { | ||||
|                       value: get(item, 'value') || item, | ||||
|                       label: | ||||
|                         get(item, 'label') || | ||||
|                         templateObject( | ||||
|                           { mainField: this.props.relation.displayedAttribute }, | ||||
|                           item | ||||
|                         ).mainField || | ||||
|                         item.id, | ||||
|                     }; | ||||
|                   } | ||||
|                 }) | ||||
|           } | ||||
|           /* eslint-enable indent */ | ||||
|           isDraggingSibling={this.props.isDraggingSibling} | ||||
|           keys={this.props.relation.alias} | ||||
|           moveAttr={this.props.moveAttr} | ||||
|           moveAttrEnd={this.props.moveAttrEnd} | ||||
|           name={this.props.relation.alias} | ||||
|           onRemove={this.handleRemove} | ||||
|           distance={1} | ||||
|           onClick={this.handleClick} | ||||
|         /> | ||||
|       </div> | ||||
|       {!isEmpty(value) && ( | ||||
|         <ul> | ||||
|           {value.map(v => ( | ||||
|             <li key={v.id}>{get(v, [mainField], '')}</li> | ||||
|           ))} | ||||
|         </ul> | ||||
|       )} | ||||
|     </> | ||||
|   ); | ||||
|     /* eslint-disable jsx-a11y/label-has-for */ | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| SelectMany.defaultProps = { | ||||
|   isDraggingSibling: false, | ||||
|   moveAttr: () => {}, | ||||
|   moveAttrEnd: () => {}, | ||||
|   value: null, | ||||
| }; | ||||
| 
 | ||||
| SelectMany.propTypes = { | ||||
|   isDraggingSibling: PropTypes.bool, | ||||
|   moveAttr: PropTypes.func, | ||||
|   moveAttrEnd: PropTypes.func, | ||||
|   onAddRelationalItem: PropTypes.func.isRequired, | ||||
|   onRedirect: PropTypes.func.isRequired, | ||||
|   onRemoveRelationItem: PropTypes.func.isRequired, | ||||
|   record: PropTypes.oneOfType([PropTypes.object, PropTypes.bool]).isRequired, | ||||
|   relation: PropTypes.object.isRequired, | ||||
|   addRelation: PropTypes.func.isRequired, | ||||
|   mainField: PropTypes.string.isRequired, | ||||
|   name: PropTypes.string.isRequired, | ||||
|   isLoading: PropTypes.bool.isRequired, | ||||
|   onInputChange: PropTypes.func.isRequired, | ||||
|   onMenuClose: PropTypes.func.isRequired, | ||||
|   onMenuScrollToBottom: PropTypes.func.isRequired, | ||||
|   options: PropTypes.array.isRequired, | ||||
|   placeholder: PropTypes.node.isRequired, | ||||
|   value: PropTypes.array, | ||||
| }; | ||||
| 
 | ||||
| export default SelectMany; | ||||
|  | ||||
| @ -0,0 +1,267 @@ | ||||
| /** | ||||
|  * | ||||
|  * SelectMany | ||||
|  * | ||||
|  */ | ||||
| 
 | ||||
| /* eslint-disable */ | ||||
| import React from 'react'; | ||||
| import Select from 'react-select'; | ||||
| import { FormattedMessage } from 'react-intl'; | ||||
| import PropTypes from 'prop-types'; | ||||
| import { | ||||
|   cloneDeep, | ||||
|   includes, | ||||
|   isArray, | ||||
|   isNull, | ||||
|   isUndefined, | ||||
|   get, | ||||
|   findIndex, | ||||
|   isEmpty, | ||||
| } from 'lodash'; | ||||
| 
 | ||||
| // Utils.
 | ||||
| import { request, templateObject } from 'strapi-helper-plugin'; | ||||
| 
 | ||||
| // Component.
 | ||||
| import SortableList from './SortableList'; | ||||
| // CSS.
 | ||||
| import styles from './styles.scss'; | ||||
| 
 | ||||
| class SelectMany extends React.PureComponent { | ||||
|   state = { | ||||
|     isLoading: true, | ||||
|     options: [], | ||||
|     start: 0, | ||||
|   }; | ||||
| 
 | ||||
|   componentDidMount() { | ||||
|     this.getOptions(''); | ||||
|   } | ||||
| 
 | ||||
|   componentDidUpdate(prevProps, prevState) { | ||||
|     if (isEmpty(prevProps.record) && !isEmpty(this.props.record)) { | ||||
|       const values = ( | ||||
|         get(this.props.record, this.props.relation.alias) || [] | ||||
|       ).map(el => el.id || el._id); | ||||
| 
 | ||||
|       const options = this.state.options.filter(el => { | ||||
|         return !values.includes(el.value.id || el.value._id); | ||||
|       }); | ||||
| 
 | ||||
|       this.state.options = options; | ||||
|     } | ||||
| 
 | ||||
|     if (prevState.start !== this.state.start) { | ||||
|       this.getOptions(''); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   getOptions = query => { | ||||
|     const params = { | ||||
|       _limit: 20, | ||||
|       _start: this.state.start, | ||||
|       source: this.props.relation.plugin || 'content-manager', | ||||
|     }; | ||||
| 
 | ||||
|     // Set `query` parameter if necessary
 | ||||
|     if (query) { | ||||
|       delete params._limit; | ||||
|       delete params._skip; | ||||
|       params[`${this.props.relation.displayedAttribute}_contains`] = query; | ||||
|     } | ||||
|     // Request URL
 | ||||
|     const requestUrl = `/content-manager/explorer/${this.props.relation.model || | ||||
|       this.props.relation.collection}`;
 | ||||
| 
 | ||||
|     // Call our request helper (see 'utils/request')
 | ||||
|     return request(requestUrl, { | ||||
|       method: 'GET', | ||||
|       params, | ||||
|     }) | ||||
|       .then(response => { | ||||
|         /* eslint-disable indent */ | ||||
|         const options = isArray(response) | ||||
|           ? response.map(item => ({ | ||||
|               value: item, | ||||
|               label: templateObject( | ||||
|                 { mainField: this.props.relation.displayedAttribute }, | ||||
|                 item | ||||
|               ).mainField, | ||||
|             })) | ||||
|           : [ | ||||
|               { | ||||
|                 value: response, | ||||
|                 label: response[this.props.relation.displayedAttribute], | ||||
|               }, | ||||
|             ]; | ||||
|         /* eslint-enable indent */ | ||||
|         const newOptions = cloneDeep(this.state.options); | ||||
|         options.map(option => { | ||||
|           // Don't add the values when searching
 | ||||
|           if ( | ||||
|             findIndex(newOptions, o => o.value.id === option.value.id) === -1 | ||||
|           ) { | ||||
|             return newOptions.push(option); | ||||
|           } | ||||
|         }); | ||||
| 
 | ||||
|         return this.setState({ | ||||
|           options: newOptions, | ||||
|           isLoading: false, | ||||
|         }); | ||||
|       }) | ||||
|       .catch(() => { | ||||
|         strapi.notification.error( | ||||
|           'content-manager.notification.error.relationship.fetch' | ||||
|         ); | ||||
|       }); | ||||
|   }; | ||||
| 
 | ||||
|   handleInputChange = value => { | ||||
|     const clonedOptions = this.state.options; | ||||
|     const filteredValues = clonedOptions.filter(data => | ||||
|       includes(data.label, value) | ||||
|     ); | ||||
| 
 | ||||
|     if (filteredValues.length === 0) { | ||||
|       return this.getOptions(value); | ||||
|     } | ||||
|   }; | ||||
| 
 | ||||
|   handleChange = value => { | ||||
|     // Remove new added value from available option;
 | ||||
|     this.state.options = this.state.options.filter( | ||||
|       el => | ||||
|         !((el.value._id || el.value.id) === (value.value.id || value.value._id)) | ||||
|     ); | ||||
| 
 | ||||
|     this.props.onAddRelationalItem({ | ||||
|       key: this.props.relation.alias, | ||||
|       value: value.value, | ||||
|     }); | ||||
|   }; | ||||
| 
 | ||||
|   handleBottomScroll = () => { | ||||
|     this.setState(prevState => { | ||||
|       return { | ||||
|         start: prevState.start + 1, | ||||
|       }; | ||||
|     }); | ||||
|   }; | ||||
| 
 | ||||
|   handleRemove = index => { | ||||
|     const values = get(this.props.record, this.props.relation.alias); | ||||
| 
 | ||||
|     // Add removed value from available option;
 | ||||
|     const toAdd = { | ||||
|       value: values[index], | ||||
|       label: templateObject( | ||||
|         { mainField: this.props.relation.displayedAttribute }, | ||||
|         values[index] | ||||
|       ).mainField, | ||||
|     }; | ||||
| 
 | ||||
|     this.setState(prevState => ({ | ||||
|       options: prevState.options.concat([toAdd]), | ||||
|     })); | ||||
| 
 | ||||
|     this.props.onRemoveRelationItem({ | ||||
|       key: this.props.relation.alias, | ||||
|       index, | ||||
|     }); | ||||
|   }; | ||||
| 
 | ||||
|   // Redirect to the edit page
 | ||||
|   handleClick = (item = {}) => { | ||||
|     this.props.onRedirect({ | ||||
|       model: this.props.relation.collection || this.props.relation.model, | ||||
|       id: item.value.id || item.value._id, | ||||
|       source: this.props.relation.plugin, | ||||
|     }); | ||||
|   }; | ||||
| 
 | ||||
|   render() { | ||||
|     const description = this.props.relation.description ? ( | ||||
|       <p>{this.props.relation.description}</p> | ||||
|     ) : ( | ||||
|       '' | ||||
|     ); | ||||
|     const value = get(this.props.record, this.props.relation.alias) || []; | ||||
| 
 | ||||
|     /* eslint-disable jsx-a11y/label-has-for */ | ||||
|     return ( | ||||
|       <div | ||||
|         className={`form-group ${styles.selectMany} ${value.length > 4 && | ||||
|           styles.selectManyUpdate}`}
 | ||||
|       > | ||||
|         <label htmlFor={this.props.relation.alias}> | ||||
|           {this.props.relation.alias} <span>({value.length})</span> | ||||
|         </label> | ||||
|         {description} | ||||
|         <Select | ||||
|           className={`${styles.select}`} | ||||
|           id={this.props.relation.alias} | ||||
|           isLoading={this.state.isLoading} | ||||
|           onChange={this.handleChange} | ||||
|           onInputChange={this.handleInputChange} | ||||
|           onMenuScrollToBottom={this.handleBottomScroll} | ||||
|           options={this.state.options} | ||||
|           placeholder={ | ||||
|             <FormattedMessage id="content-manager.containers.Edit.addAnItem" /> | ||||
|           } | ||||
|         /> | ||||
|         <SortableList | ||||
|           items={ | ||||
|             /* eslint-disable indent */ | ||||
|             isNull(value) || isUndefined(value) || value.size === 0 | ||||
|               ? null | ||||
|               : value.map(item => { | ||||
|                   if (item) { | ||||
|                     return { | ||||
|                       value: get(item, 'value') || item, | ||||
|                       label: | ||||
|                         get(item, 'label') || | ||||
|                         templateObject( | ||||
|                           { mainField: this.props.relation.displayedAttribute }, | ||||
|                           item | ||||
|                         ).mainField || | ||||
|                         item.id, | ||||
|                     }; | ||||
|                   } | ||||
|                 }) | ||||
|           } | ||||
|           /* eslint-enable indent */ | ||||
|           isDraggingSibling={this.props.isDraggingSibling} | ||||
|           keys={this.props.relation.alias} | ||||
|           moveAttr={this.props.moveAttr} | ||||
|           moveAttrEnd={this.props.moveAttrEnd} | ||||
|           name={this.props.relation.alias} | ||||
|           onRemove={this.handleRemove} | ||||
|           distance={1} | ||||
|           onClick={this.handleClick} | ||||
|         /> | ||||
|       </div> | ||||
|     ); | ||||
|     /* eslint-disable jsx-a11y/label-has-for */ | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| SelectMany.defaultProps = { | ||||
|   isDraggingSibling: false, | ||||
|   moveAttr: () => {}, | ||||
|   moveAttrEnd: () => {}, | ||||
| }; | ||||
| 
 | ||||
| SelectMany.propTypes = { | ||||
|   isDraggingSibling: PropTypes.bool, | ||||
|   moveAttr: PropTypes.func, | ||||
|   moveAttrEnd: PropTypes.func, | ||||
|   onAddRelationalItem: PropTypes.func.isRequired, | ||||
|   onRedirect: PropTypes.func.isRequired, | ||||
|   onRemoveRelationItem: PropTypes.func.isRequired, | ||||
|   record: PropTypes.oneOfType([PropTypes.object, PropTypes.bool]).isRequired, | ||||
|   relation: PropTypes.object.isRequired, | ||||
| }; | ||||
| 
 | ||||
| export default SelectMany; | ||||
| @ -1,135 +1,53 @@ | ||||
| import React, { useState, useEffect, useRef } from 'react'; | ||||
| import React from 'react'; | ||||
| import PropTypes from 'prop-types'; | ||||
| import { FormattedMessage } from 'react-intl'; | ||||
| import { Link } from 'react-router-dom'; | ||||
| import { get, isEmpty, isNull } from 'lodash'; | ||||
| import { request } from 'strapi-helper-plugin'; | ||||
| import { get, isNull } from 'lodash'; | ||||
| 
 | ||||
| import Select from 'react-select'; | ||||
| 
 | ||||
| import pluginId from '../../pluginId'; | ||||
| 
 | ||||
| import { Nav, Wrapper } from './components'; | ||||
| 
 | ||||
| function SelectOne({ | ||||
|   description, | ||||
|   label, | ||||
|   mainField, | ||||
|   name, | ||||
|   isLoading, | ||||
|   onChange, | ||||
|   pathname, | ||||
|   search, | ||||
|   targetModel, | ||||
|   plugin, | ||||
|   onInputChange, | ||||
|   onMenuClose, | ||||
|   onMenuScrollToBottom, | ||||
|   options, | ||||
|   placeholder, | ||||
|   value, | ||||
| }) { | ||||
|   const [state, setState] = useState({ | ||||
|     _q: '', | ||||
|     _limit: 8, | ||||
|     _start: 0, | ||||
|     source: isEmpty(plugin) ? 'content-manager' : plugin, | ||||
|   }); | ||||
|   const [options, setOptions] = useState([]); | ||||
|   const [isLoading, setIsLoading] = useState(true); | ||||
|   const ref = useRef(); | ||||
| 
 | ||||
|   ref.current = async (uid, params = state) => { | ||||
|     try { | ||||
|       const requestUrl = `/${pluginId}/explorer/${uid}`; | ||||
| 
 | ||||
|       if (isEmpty(params._q)) { | ||||
|         delete params._q; | ||||
|       } | ||||
| 
 | ||||
|       const data = await request(requestUrl, { | ||||
|         method: 'GET', | ||||
|         params: params, | ||||
|       }); | ||||
| 
 | ||||
|       setOptions( | ||||
|         data.map(obj => { | ||||
|           return { value: obj, label: obj[mainField] }; | ||||
|         }) | ||||
|       ); | ||||
|       setIsLoading(false); | ||||
|     } catch (err) { | ||||
|       console.log({ err }); | ||||
| 
 | ||||
|       strapi.notification.error('notification.error'); | ||||
|     } | ||||
|   }; | ||||
| 
 | ||||
|   useEffect(() => { | ||||
|     ref.current(targetModel); | ||||
|   }, [ref, targetModel, state._start]); | ||||
| 
 | ||||
|   const nextSearch = `${pathname}${search}`; | ||||
|   const to = `/plugins/${pluginId}/${targetModel}/${ | ||||
|     value ? value.id : null | ||||
|   }?redirectUrl=${nextSearch}`;
 | ||||
|   const link = | ||||
|     value === null || | ||||
|     value === undefined || | ||||
|     ['role', 'permission'].includes(targetModel) ? null : ( | ||||
|       <Link to={to}> | ||||
|         <FormattedMessage id="content-manager.containers.Edit.seeDetails" /> | ||||
|       </Link> | ||||
|     ); | ||||
| 
 | ||||
|   const onInputChange = inputValue => { | ||||
|     setState(prevState => ({ ...prevState, _q: inputValue })); | ||||
| 
 | ||||
|     return inputValue; | ||||
|   }; | ||||
| 
 | ||||
|   const onMenuScrollToBottom = () => { | ||||
|     setState(prevState => ({ ...prevState, _start: prevState._start + 1 })); | ||||
|   }; | ||||
| 
 | ||||
|   return ( | ||||
|     <Wrapper className="form-group"> | ||||
|       <Nav> | ||||
|         <label htmlFor={name}>{label}</label> | ||||
|         {link} | ||||
|       </Nav> | ||||
|       {!isEmpty(description) && <p>{description}</p>} | ||||
|     <Select | ||||
|       id={name} | ||||
|       isLoading={isLoading} | ||||
|       isClearable | ||||
|       options={options} | ||||
|         onChange={value => { | ||||
|           onChange({ target: { name, value: value ? value.value : value } }); | ||||
|         }} | ||||
|       onChange={onChange} | ||||
|       onInputChange={onInputChange} | ||||
|         onMenuClose={() => { | ||||
|           setState(prevState => ({ ...prevState, _start: 0 })); | ||||
|         }} | ||||
|       onMenuClose={onMenuClose} | ||||
|       onMenuScrollToBottom={onMenuScrollToBottom} | ||||
|       placeholder={placeholder} | ||||
|       value={ | ||||
|         isNull(value) ? null : { label: get(value, [mainField], ''), value } | ||||
|       } | ||||
|     /> | ||||
|     </Wrapper> | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| SelectOne.defaultProps = { | ||||
|   description: '', | ||||
|   label: '', | ||||
|   plugin: '', | ||||
|   value: null, | ||||
| }; | ||||
| 
 | ||||
| SelectOne.propTypes = { | ||||
|   description: PropTypes.string, | ||||
|   label: PropTypes.string, | ||||
|   mainField: PropTypes.string.isRequired, | ||||
|   name: PropTypes.string.isRequired, | ||||
|   isLoading: PropTypes.bool.isRequired, | ||||
|   onChange: PropTypes.func.isRequired, | ||||
|   pathname: PropTypes.string.isRequired, | ||||
|   plugin: PropTypes.string, | ||||
|   search: PropTypes.string.isRequired, | ||||
|   targetModel: PropTypes.string.isRequired, | ||||
|   onInputChange: PropTypes.func.isRequired, | ||||
|   onMenuClose: PropTypes.func.isRequired, | ||||
|   onMenuScrollToBottom: PropTypes.func.isRequired, | ||||
|   options: PropTypes.array.isRequired, | ||||
|   placeholder: PropTypes.node.isRequired, | ||||
|   value: PropTypes.object, | ||||
| }; | ||||
| 
 | ||||
|  | ||||
| @ -0,0 +1,51 @@ | ||||
| import styled from 'styled-components'; | ||||
| 
 | ||||
| const Wrapper = styled.div` | ||||
|   position: relative; | ||||
| 
 | ||||
|   label { | ||||
|     font-size: 1.3rem; | ||||
|     font-weight: 500; | ||||
|     margin-top: 3px; | ||||
|   } | ||||
| 
 | ||||
|   nav + div { | ||||
|     height: 34px; | ||||
|     margin: 3px 0 26px; | ||||
| 
 | ||||
|     > div { | ||||
|       box-shadow: none !important; | ||||
|       border-color: #e3e9f3 !important; | ||||
| 
 | ||||
|       > span:first-of-type { | ||||
|         > div:first-of-type { | ||||
|           color: #9ea7b8; | ||||
|         } | ||||
|       } | ||||
| 
 | ||||
|       > span:last-of-type { | ||||
|         span { | ||||
|           border-color: #b3b5b9 transparent transparent; | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| `;
 | ||||
| 
 | ||||
| const Nav = styled.nav` | ||||
|   display: flex; | ||||
|   justify-content: space-between; | ||||
| 
 | ||||
|   a { | ||||
|     color: #007eff !important; | ||||
|     font-size: 1.3rem; | ||||
|     padding-top: 3px; | ||||
| 
 | ||||
|     &:hover { | ||||
|       text-decoration: underline !important; | ||||
|       cursor: pointer; | ||||
|     } | ||||
|   } | ||||
| `;
 | ||||
| 
 | ||||
| export { Nav, Wrapper }; | ||||
| @ -0,0 +1,180 @@ | ||||
| import React, { useState, useEffect, useRef } from 'react'; | ||||
| import PropTypes from 'prop-types'; | ||||
| import { FormattedMessage } from 'react-intl'; | ||||
| import { Link } from 'react-router-dom'; | ||||
| import { isEmpty } from 'lodash'; | ||||
| import { request } from 'strapi-helper-plugin'; | ||||
| 
 | ||||
| import pluginId from '../../pluginId'; | ||||
| 
 | ||||
| import SelectOne from '../SelectOne'; | ||||
| import SelectMany from '../SelectMany'; | ||||
| 
 | ||||
| import { Nav, Wrapper } from './components'; | ||||
| 
 | ||||
| function SelectWrapper({ | ||||
|   addRelation, | ||||
|   label, | ||||
|   mainField, | ||||
|   name, | ||||
|   onChange, | ||||
|   pathname, | ||||
|   relationType, | ||||
|   search, | ||||
|   targetModel, | ||||
|   plugin, | ||||
|   value, | ||||
| }) { | ||||
|   const [state, setState] = useState({ | ||||
|     _q: '', | ||||
|     _limit: 8, | ||||
|     _start: 0, | ||||
|     source: isEmpty(plugin) ? 'content-manager' : plugin, | ||||
|   }); | ||||
|   const [options, setOptions] = useState([]); | ||||
|   const [isLoading, setIsLoading] = useState(true); | ||||
|   const ref = useRef(); | ||||
|   const startRef = useRef(); | ||||
|   startRef.current = state._start; | ||||
| 
 | ||||
|   ref.current = async (uid, params = state) => { | ||||
|     try { | ||||
|       const requestUrl = `/${pluginId}/explorer/${uid}`; | ||||
| 
 | ||||
|       if (isEmpty(params._q)) { | ||||
|         delete params._q; | ||||
|       } | ||||
| 
 | ||||
|       const data = await request(requestUrl, { | ||||
|         method: 'GET', | ||||
|         params: params, | ||||
|       }); | ||||
|       const formattedData = data.map(obj => { | ||||
|         return { value: obj, label: obj[mainField] }; | ||||
|       }); | ||||
| 
 | ||||
|       if (!isEmpty(params._q)) { | ||||
|         setOptions(formattedData); | ||||
| 
 | ||||
|         return; | ||||
|       } | ||||
| 
 | ||||
|       setOptions(prevState => | ||||
|         prevState.concat(formattedData).filter((obj, index) => { | ||||
|           const objIndex = prevState.findIndex( | ||||
|             el => el.value.id === obj.value.id | ||||
|           ); | ||||
| 
 | ||||
|           if (objIndex === -1) { | ||||
|             return true; | ||||
|           } | ||||
|           return ( | ||||
|             prevState.findIndex(el => el.value.id === obj.value.id) === index | ||||
|           ); | ||||
|         }) | ||||
|       ); | ||||
|       setIsLoading(false); | ||||
|     } catch (err) { | ||||
|       strapi.notification.error('notification.error'); | ||||
|     } | ||||
|   }; | ||||
| 
 | ||||
|   useEffect(() => { | ||||
|     ref.current(targetModel); | ||||
| 
 | ||||
|     return () => {}; | ||||
|   }, [ref, targetModel, state._start, state._q]); | ||||
| 
 | ||||
|   const onInputChange = inputValue => { | ||||
|     setState(prevState => { | ||||
|       if (prevState._q === inputValue) { | ||||
|         return prevState; | ||||
|       } | ||||
| 
 | ||||
|       return { ...prevState, _q: inputValue }; | ||||
|     }); | ||||
| 
 | ||||
|     return inputValue; | ||||
|   }; | ||||
| 
 | ||||
|   const onMenuScrollToBottom = () => { | ||||
|     setState(prevState => ({ ...prevState, _start: prevState._start + 1 })); | ||||
|   }; | ||||
|   const isSingle = [ | ||||
|     'oneWay', | ||||
|     'oneToOne', | ||||
|     'manyToOne', | ||||
|     'oneToManyMorph', | ||||
|     'oneToOneMorph', | ||||
|   ].includes(relationType); | ||||
|   const nextSearch = `${pathname}${search}`; | ||||
|   const to = `/plugins/${pluginId}/${targetModel}/${ | ||||
|     value ? value.id : null | ||||
|   }?redirectUrl=${nextSearch}`;
 | ||||
|   const link = | ||||
|     value === null || | ||||
|     value === undefined || | ||||
|     ['role', 'permission'].includes(targetModel) ? null : ( | ||||
|       <Link to={to}> | ||||
|         <FormattedMessage id="content-manager.containers.Edit.seeDetails" /> | ||||
|       </Link> | ||||
|     ); | ||||
|   const Component = isSingle ? SelectOne : SelectMany; | ||||
| 
 | ||||
|   return ( | ||||
|     <Wrapper className="form-group"> | ||||
|       <Nav> | ||||
|         <label htmlFor={name}>{label}</label> | ||||
|         {isSingle && link} | ||||
|       </Nav> | ||||
|       <Component | ||||
|         addRelation={value => { | ||||
|           addRelation({ target: { name, value } }); | ||||
|         }} | ||||
|         id={name} | ||||
|         isLoading={isLoading} | ||||
|         isClearable | ||||
|         mainField={mainField} | ||||
|         name={name} | ||||
|         options={options} | ||||
|         onChange={value => { | ||||
|           onChange({ target: { name, value: value ? value.value : value } }); | ||||
|         }} | ||||
|         onInputChange={onInputChange} | ||||
|         onMenuClose={() => { | ||||
|           setState(prevState => ({ ...prevState, _q: '', _start: 0 })); | ||||
|         }} | ||||
|         onMenuScrollToBottom={onMenuScrollToBottom} | ||||
|         placeholder={ | ||||
|           <FormattedMessage id={`${pluginId}.containers.Edit.addAnItem`} /> | ||||
|         } | ||||
|         value={value} | ||||
|       /> | ||||
|       <div style={{ marginBottom: 26 }} /> | ||||
|     </Wrapper> | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| SelectWrapper.defaultProps = { | ||||
|   description: '', | ||||
|   label: '', | ||||
|   plugin: '', | ||||
|   value: null, | ||||
| }; | ||||
| 
 | ||||
| SelectWrapper.propTypes = { | ||||
|   addRelation: PropTypes.func.isRequired, | ||||
|   description: PropTypes.string, | ||||
|   label: PropTypes.string, | ||||
|   mainField: PropTypes.string.isRequired, | ||||
|   name: PropTypes.string.isRequired, | ||||
|   onChange: PropTypes.func.isRequired, | ||||
|   pathname: PropTypes.string.isRequired, | ||||
|   plugin: PropTypes.string, | ||||
|   relationType: PropTypes.string.isRequired, | ||||
|   search: PropTypes.string.isRequired, | ||||
|   targetModel: PropTypes.string.isRequired, | ||||
|   value: PropTypes.oneOfType([PropTypes.object, PropTypes.array]), | ||||
| }; | ||||
| 
 | ||||
| export default SelectWrapper; | ||||
| @ -39,7 +39,7 @@ const getInputType = (type = '') => { | ||||
|   } | ||||
| }; | ||||
| 
 | ||||
| function Inputs({ keys, layout, modifiedData, name, onChange }) { | ||||
| function Inputs({ autoFocus, keys, layout, modifiedData, name, onChange }) { | ||||
|   const attribute = get(layout, ['schema', 'attributes', name], {}); | ||||
|   const { model, collection } = attribute; | ||||
|   const isMedia = | ||||
| @ -68,6 +68,7 @@ function Inputs({ keys, layout, modifiedData, name, onChange }) { | ||||
|   return ( | ||||
|     <InputsIndex | ||||
|       {...metadata} | ||||
|       autoFocus={autoFocus} | ||||
|       inputDescription={description} | ||||
|       inputStyle={inputStyle} | ||||
|       customInputs={{ json: InputJSONWithErrors, wysiwyg: WysiwygWithErrors }} | ||||
| @ -82,7 +83,12 @@ function Inputs({ keys, layout, modifiedData, name, onChange }) { | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| Inputs.defaultProps = { | ||||
|   autoFocus: false, | ||||
| }; | ||||
| 
 | ||||
| Inputs.propTypes = { | ||||
|   autoFocus: PropTypes.bool, | ||||
|   keys: PropTypes.string.isRequired, | ||||
|   layout: PropTypes.object.isRequired, | ||||
|   modifiedData: PropTypes.object.isRequired, | ||||
|  | ||||
| @ -15,7 +15,6 @@ import { | ||||
| import pluginId from '../../pluginId'; | ||||
| 
 | ||||
| import Container from '../../components/Container'; | ||||
| import SelectOne from '../../components/SelectOne'; | ||||
| 
 | ||||
| import { LinkWrapper, MainWrapper, SubWrapper } from './components'; | ||||
| import Group from './Group'; | ||||
| @ -23,6 +22,7 @@ import Inputs from './Inputs'; | ||||
| 
 | ||||
| import init, { setDefaultForm } from './init'; | ||||
| import reducer, { initialState } from './reducer'; | ||||
| import SelectWrapper from '../../components/SelectWrapper'; | ||||
| 
 | ||||
| const getRequestUrl = path => `/${pluginId}/explorer/${path}`; | ||||
| 
 | ||||
| @ -371,9 +371,10 @@ function EditView({ | ||||
| 
 | ||||
|                   return ( | ||||
|                     <div key={key} className="row"> | ||||
|                       {fieldsRow.map(({ name }) => { | ||||
|                       {fieldsRow.map(({ name }, index) => { | ||||
|                         return ( | ||||
|                           <Inputs | ||||
|                             autoFocus={key === 0 && index === 0} | ||||
|                             key={name} | ||||
|                             keys={name} | ||||
|                             layout={layout} | ||||
| @ -413,22 +414,19 @@ function EditView({ | ||||
|                         {} | ||||
|                       ); | ||||
|                       const value = get(modifiedData, [relationName], null); | ||||
|                       const Component = [ | ||||
|                         'oneWay', | ||||
|                         'oneToOne', | ||||
|                         'manyToOne', | ||||
|                         'oneToManyMorph', | ||||
|                         'oneToOneMorph', | ||||
|                       ].includes(relation.relationType) | ||||
|                         ? SelectOne | ||||
|                         : // eslint-disable-next-line react/display-name
 | ||||
|                           () => <div>SelectMany</div>; | ||||
| 
 | ||||
|                       return ( | ||||
|                         <Component | ||||
|                           key={relationName} | ||||
|                         <SelectWrapper | ||||
|                           {...relation} | ||||
|                           {...relationMetas} | ||||
|                           key={relationName} | ||||
|                           addRelation={({ target: { name, value } }) => { | ||||
|                             dispatch({ | ||||
|                               type: 'ADD_RELATION', | ||||
|                               keys: name.split('.'), | ||||
|                               value, | ||||
|                             }); | ||||
|                           }} | ||||
|                           name={relationName} | ||||
|                           onChange={({ target: { name, value } }) => { | ||||
|                             dispatch({ | ||||
| @ -438,6 +436,7 @@ function EditView({ | ||||
|                             }); | ||||
|                           }} | ||||
|                           pathname={pathname} | ||||
|                           relationType={relation.relationType} | ||||
|                           search={search} | ||||
|                           value={value} | ||||
|                         /> | ||||
|  | ||||
| @ -44,6 +44,20 @@ function reducer(state, action) { | ||||
|             : { _temp__id: 0 }, | ||||
|         ]); | ||||
|       }); | ||||
|     case 'ADD_RELATION': | ||||
|       return state.updateIn(['modifiedData', ...action.keys], list => { | ||||
|         if (!action.value) { | ||||
|           return list; | ||||
|         } | ||||
| 
 | ||||
|         const el = action.value[0].value; | ||||
| 
 | ||||
|         if (list) { | ||||
|           return list.push(fromJS(el)); | ||||
|         } | ||||
| 
 | ||||
|         return fromJS([el]); | ||||
|       }); | ||||
|     case 'GET_DATA_SUCCEEDED': | ||||
|       return state | ||||
|         .update('initialData', () => fromJS(action.data)) | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 soupette
						soupette