mirror of
https://github.com/strapi/strapi.git
synced 2025-10-29 17:04:13 +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 React from 'react';
|
||||||
import Select from 'react-select';
|
|
||||||
import { FormattedMessage } from 'react-intl';
|
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import {
|
import { get, isEmpty } from 'lodash';
|
||||||
cloneDeep,
|
|
||||||
includes,
|
|
||||||
isArray,
|
|
||||||
isNull,
|
|
||||||
isUndefined,
|
|
||||||
get,
|
|
||||||
findIndex,
|
|
||||||
isEmpty,
|
|
||||||
} from 'lodash';
|
|
||||||
|
|
||||||
// Utils.
|
import Select from 'react-select';
|
||||||
import { request, templateObject } from 'strapi-helper-plugin';
|
|
||||||
|
|
||||||
// Component.
|
function SelectMany({
|
||||||
import SortableList from './SortableList';
|
addRelation,
|
||||||
// CSS.
|
mainField,
|
||||||
import styles from './styles.scss';
|
name,
|
||||||
|
isLoading,
|
||||||
class SelectMany extends React.PureComponent {
|
onInputChange,
|
||||||
state = {
|
onMenuClose,
|
||||||
isLoading: true,
|
onMenuScrollToBottom,
|
||||||
options: [],
|
options,
|
||||||
start: 0,
|
placeholder,
|
||||||
};
|
value,
|
||||||
|
}) {
|
||||||
componentDidMount() {
|
return (
|
||||||
this.getOptions('');
|
<>
|
||||||
}
|
<Select
|
||||||
|
id={name}
|
||||||
componentDidUpdate(prevProps, prevState) {
|
filterOption={el => {
|
||||||
if (isEmpty(prevProps.record) && !isEmpty(this.props.record)) {
|
if (isEmpty(value)) {
|
||||||
const values = (
|
return true;
|
||||||
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({
|
return value.findIndex(obj => obj.id === el.value.id) === -1;
|
||||||
options: newOptions,
|
}}
|
||||||
isLoading: false,
|
isLoading={isLoading}
|
||||||
});
|
isMulti
|
||||||
})
|
isSearchable
|
||||||
.catch(() => {
|
options={options}
|
||||||
strapi.notification.error(
|
onChange={addRelation}
|
||||||
'content-manager.notification.error.relationship.fetch'
|
onInputChange={onInputChange}
|
||||||
);
|
onMenuClose={onMenuClose}
|
||||||
});
|
onMenuScrollToBottom={onMenuScrollToBottom}
|
||||||
};
|
placeholder={placeholder}
|
||||||
|
value={[]}
|
||||||
handleInputChange = value => {
|
/>
|
||||||
const clonedOptions = this.state.options;
|
{!isEmpty(value) && (
|
||||||
const filteredValues = clonedOptions.filter(data =>
|
<ul>
|
||||||
includes(data.label, value)
|
{value.map(v => (
|
||||||
);
|
<li key={v.id}>{get(v, [mainField], '')}</li>
|
||||||
|
))}
|
||||||
if (filteredValues.length === 0) {
|
</ul>
|
||||||
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 = {
|
SelectMany.defaultProps = {
|
||||||
isDraggingSibling: false,
|
value: null,
|
||||||
moveAttr: () => {},
|
|
||||||
moveAttrEnd: () => {},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
SelectMany.propTypes = {
|
SelectMany.propTypes = {
|
||||||
isDraggingSibling: PropTypes.bool,
|
addRelation: PropTypes.func.isRequired,
|
||||||
moveAttr: PropTypes.func,
|
mainField: PropTypes.string.isRequired,
|
||||||
moveAttrEnd: PropTypes.func,
|
name: PropTypes.string.isRequired,
|
||||||
onAddRelationalItem: PropTypes.func.isRequired,
|
isLoading: PropTypes.bool.isRequired,
|
||||||
onRedirect: PropTypes.func.isRequired,
|
onInputChange: PropTypes.func.isRequired,
|
||||||
onRemoveRelationItem: PropTypes.func.isRequired,
|
onMenuClose: PropTypes.func.isRequired,
|
||||||
record: PropTypes.oneOfType([PropTypes.object, PropTypes.bool]).isRequired,
|
onMenuScrollToBottom: PropTypes.func.isRequired,
|
||||||
relation: PropTypes.object.isRequired,
|
options: PropTypes.array.isRequired,
|
||||||
|
placeholder: PropTypes.node.isRequired,
|
||||||
|
value: PropTypes.array,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default SelectMany;
|
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 PropTypes from 'prop-types';
|
||||||
import { FormattedMessage } from 'react-intl';
|
import { get, isNull } from 'lodash';
|
||||||
import { Link } from 'react-router-dom';
|
|
||||||
import { get, isEmpty, isNull } from 'lodash';
|
|
||||||
import { request } from 'strapi-helper-plugin';
|
|
||||||
import Select from 'react-select';
|
import Select from 'react-select';
|
||||||
|
|
||||||
import pluginId from '../../pluginId';
|
|
||||||
|
|
||||||
import { Nav, Wrapper } from './components';
|
|
||||||
|
|
||||||
function SelectOne({
|
function SelectOne({
|
||||||
description,
|
|
||||||
label,
|
|
||||||
mainField,
|
mainField,
|
||||||
name,
|
name,
|
||||||
|
isLoading,
|
||||||
onChange,
|
onChange,
|
||||||
pathname,
|
onInputChange,
|
||||||
search,
|
onMenuClose,
|
||||||
targetModel,
|
onMenuScrollToBottom,
|
||||||
plugin,
|
options,
|
||||||
|
placeholder,
|
||||||
value,
|
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 (
|
return (
|
||||||
<Wrapper className="form-group">
|
<Select
|
||||||
<Nav>
|
id={name}
|
||||||
<label htmlFor={name}>{label}</label>
|
isLoading={isLoading}
|
||||||
{link}
|
isClearable
|
||||||
</Nav>
|
options={options}
|
||||||
{!isEmpty(description) && <p>{description}</p>}
|
onChange={onChange}
|
||||||
<Select
|
onInputChange={onInputChange}
|
||||||
id={name}
|
onMenuClose={onMenuClose}
|
||||||
isLoading={isLoading}
|
onMenuScrollToBottom={onMenuScrollToBottom}
|
||||||
isClearable
|
placeholder={placeholder}
|
||||||
options={options}
|
value={
|
||||||
onChange={value => {
|
isNull(value) ? null : { label: get(value, [mainField], ''), value }
|
||||||
onChange({ target: { name, value: value ? value.value : value } });
|
}
|
||||||
}}
|
/>
|
||||||
onInputChange={onInputChange}
|
|
||||||
onMenuClose={() => {
|
|
||||||
setState(prevState => ({ ...prevState, _start: 0 }));
|
|
||||||
}}
|
|
||||||
onMenuScrollToBottom={onMenuScrollToBottom}
|
|
||||||
value={
|
|
||||||
isNull(value) ? null : { label: get(value, [mainField], ''), value }
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</Wrapper>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
SelectOne.defaultProps = {
|
SelectOne.defaultProps = {
|
||||||
description: '',
|
|
||||||
label: '',
|
|
||||||
plugin: '',
|
|
||||||
value: null,
|
value: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
SelectOne.propTypes = {
|
SelectOne.propTypes = {
|
||||||
description: PropTypes.string,
|
|
||||||
label: PropTypes.string,
|
|
||||||
mainField: PropTypes.string.isRequired,
|
mainField: PropTypes.string.isRequired,
|
||||||
name: PropTypes.string.isRequired,
|
name: PropTypes.string.isRequired,
|
||||||
|
isLoading: PropTypes.bool.isRequired,
|
||||||
onChange: PropTypes.func.isRequired,
|
onChange: PropTypes.func.isRequired,
|
||||||
pathname: PropTypes.string.isRequired,
|
onInputChange: PropTypes.func.isRequired,
|
||||||
plugin: PropTypes.string,
|
onMenuClose: PropTypes.func.isRequired,
|
||||||
search: PropTypes.string.isRequired,
|
onMenuScrollToBottom: PropTypes.func.isRequired,
|
||||||
targetModel: PropTypes.string.isRequired,
|
options: PropTypes.array.isRequired,
|
||||||
|
placeholder: PropTypes.node.isRequired,
|
||||||
value: PropTypes.object,
|
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 attribute = get(layout, ['schema', 'attributes', name], {});
|
||||||
const { model, collection } = attribute;
|
const { model, collection } = attribute;
|
||||||
const isMedia =
|
const isMedia =
|
||||||
@ -68,6 +68,7 @@ function Inputs({ keys, layout, modifiedData, name, onChange }) {
|
|||||||
return (
|
return (
|
||||||
<InputsIndex
|
<InputsIndex
|
||||||
{...metadata}
|
{...metadata}
|
||||||
|
autoFocus={autoFocus}
|
||||||
inputDescription={description}
|
inputDescription={description}
|
||||||
inputStyle={inputStyle}
|
inputStyle={inputStyle}
|
||||||
customInputs={{ json: InputJSONWithErrors, wysiwyg: WysiwygWithErrors }}
|
customInputs={{ json: InputJSONWithErrors, wysiwyg: WysiwygWithErrors }}
|
||||||
@ -82,7 +83,12 @@ function Inputs({ keys, layout, modifiedData, name, onChange }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Inputs.defaultProps = {
|
||||||
|
autoFocus: false,
|
||||||
|
};
|
||||||
|
|
||||||
Inputs.propTypes = {
|
Inputs.propTypes = {
|
||||||
|
autoFocus: PropTypes.bool,
|
||||||
keys: PropTypes.string.isRequired,
|
keys: PropTypes.string.isRequired,
|
||||||
layout: PropTypes.object.isRequired,
|
layout: PropTypes.object.isRequired,
|
||||||
modifiedData: PropTypes.object.isRequired,
|
modifiedData: PropTypes.object.isRequired,
|
||||||
|
|||||||
@ -15,7 +15,6 @@ import {
|
|||||||
import pluginId from '../../pluginId';
|
import pluginId from '../../pluginId';
|
||||||
|
|
||||||
import Container from '../../components/Container';
|
import Container from '../../components/Container';
|
||||||
import SelectOne from '../../components/SelectOne';
|
|
||||||
|
|
||||||
import { LinkWrapper, MainWrapper, SubWrapper } from './components';
|
import { LinkWrapper, MainWrapper, SubWrapper } from './components';
|
||||||
import Group from './Group';
|
import Group from './Group';
|
||||||
@ -23,6 +22,7 @@ import Inputs from './Inputs';
|
|||||||
|
|
||||||
import init, { setDefaultForm } from './init';
|
import init, { setDefaultForm } from './init';
|
||||||
import reducer, { initialState } from './reducer';
|
import reducer, { initialState } from './reducer';
|
||||||
|
import SelectWrapper from '../../components/SelectWrapper';
|
||||||
|
|
||||||
const getRequestUrl = path => `/${pluginId}/explorer/${path}`;
|
const getRequestUrl = path => `/${pluginId}/explorer/${path}`;
|
||||||
|
|
||||||
@ -371,9 +371,10 @@ function EditView({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={key} className="row">
|
<div key={key} className="row">
|
||||||
{fieldsRow.map(({ name }) => {
|
{fieldsRow.map(({ name }, index) => {
|
||||||
return (
|
return (
|
||||||
<Inputs
|
<Inputs
|
||||||
|
autoFocus={key === 0 && index === 0}
|
||||||
key={name}
|
key={name}
|
||||||
keys={name}
|
keys={name}
|
||||||
layout={layout}
|
layout={layout}
|
||||||
@ -413,22 +414,19 @@ function EditView({
|
|||||||
{}
|
{}
|
||||||
);
|
);
|
||||||
const value = get(modifiedData, [relationName], null);
|
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 (
|
return (
|
||||||
<Component
|
<SelectWrapper
|
||||||
key={relationName}
|
|
||||||
{...relation}
|
{...relation}
|
||||||
{...relationMetas}
|
{...relationMetas}
|
||||||
|
key={relationName}
|
||||||
|
addRelation={({ target: { name, value } }) => {
|
||||||
|
dispatch({
|
||||||
|
type: 'ADD_RELATION',
|
||||||
|
keys: name.split('.'),
|
||||||
|
value,
|
||||||
|
});
|
||||||
|
}}
|
||||||
name={relationName}
|
name={relationName}
|
||||||
onChange={({ target: { name, value } }) => {
|
onChange={({ target: { name, value } }) => {
|
||||||
dispatch({
|
dispatch({
|
||||||
@ -438,6 +436,7 @@ function EditView({
|
|||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
pathname={pathname}
|
pathname={pathname}
|
||||||
|
relationType={relation.relationType}
|
||||||
search={search}
|
search={search}
|
||||||
value={value}
|
value={value}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -44,6 +44,20 @@ function reducer(state, action) {
|
|||||||
: { _temp__id: 0 },
|
: { _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':
|
case 'GET_DATA_SUCCEEDED':
|
||||||
return state
|
return state
|
||||||
.update('initialData', () => fromJS(action.data))
|
.update('initialData', () => fromJS(action.data))
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user