mirror of
https://github.com/strapi/strapi.git
synced 2025-10-12 16:43:39 +00:00
240 lines
5.8 KiB
JavaScript
240 lines
5.8 KiB
JavaScript
import React, { useState, useEffect, useRef, memo } from 'react';
|
|
import PropTypes from 'prop-types';
|
|
import { FormattedMessage } from 'react-intl';
|
|
import { Link } from 'react-router-dom';
|
|
import { isArray, isEmpty, cloneDeep } from 'lodash';
|
|
import { request } from 'strapi-helper-plugin';
|
|
|
|
import pluginId from '../../pluginId';
|
|
import { useEditView } from '../../contexts/EditView';
|
|
|
|
import SelectOne from '../SelectOne';
|
|
import SelectMany from '../SelectMany';
|
|
import { Nav, Wrapper } from './components';
|
|
|
|
function SelectWrapper({
|
|
description,
|
|
editable,
|
|
label,
|
|
mainField,
|
|
name,
|
|
relationType,
|
|
targetModel,
|
|
placeholder,
|
|
plugin,
|
|
value,
|
|
}) {
|
|
const {
|
|
addRelation,
|
|
moveRelation,
|
|
onChange,
|
|
onRemove,
|
|
pathname,
|
|
search,
|
|
} = useEditView();
|
|
const source = isEmpty(plugin) ? 'content-manager' : plugin;
|
|
const [state, setState] = useState({
|
|
_q: '',
|
|
_limit: 20,
|
|
_start: 0,
|
|
source,
|
|
});
|
|
const [options, setOptions] = useState([]);
|
|
const [isLoading, setIsLoading] = useState(true);
|
|
const abortController = new AbortController();
|
|
const { signal } = abortController;
|
|
const ref = useRef();
|
|
const startRef = useRef();
|
|
startRef.current = state._start;
|
|
|
|
ref.current = async () => {
|
|
try {
|
|
const params = cloneDeep(state);
|
|
const requestUrl = `/${pluginId}/explorer/${targetModel}`;
|
|
|
|
if (isEmpty(params._q)) {
|
|
delete params._q;
|
|
}
|
|
|
|
const data = await request(requestUrl, {
|
|
method: 'GET',
|
|
params: params,
|
|
signal,
|
|
});
|
|
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) {
|
|
if (err.code !== 20) {
|
|
strapi.notification.error('notification.error');
|
|
}
|
|
}
|
|
};
|
|
|
|
useEffect(() => {
|
|
ref.current();
|
|
|
|
return () => {
|
|
abortController.abort();
|
|
};
|
|
}, [ref]);
|
|
|
|
useEffect(() => {
|
|
if (state._q !== '') {
|
|
ref.current();
|
|
}
|
|
|
|
return () => {
|
|
abortController.abort();
|
|
};
|
|
}, [state._q]);
|
|
|
|
useEffect(() => {
|
|
if (state._start !== 0) {
|
|
ref.current();
|
|
}
|
|
|
|
return () => {
|
|
abortController.abort();
|
|
};
|
|
}, [state._start]);
|
|
|
|
const onInputChange = (inputValue, { action }) => {
|
|
if (action === 'input-change') {
|
|
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
|
|
}?source=${source}&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;
|
|
const associationsLength = isArray(value) ? value.length : 0;
|
|
|
|
return (
|
|
<Wrapper className="form-group">
|
|
<Nav>
|
|
<div>
|
|
<label htmlFor={name}>
|
|
{label}
|
|
{!isSingle && (
|
|
<span style={{ fontWeight: 400, fontSize: 12 }}>
|
|
({associationsLength})
|
|
</span>
|
|
)}
|
|
</label>
|
|
{isSingle && link}
|
|
</div>
|
|
{!isEmpty(description) && <p className="description">{description}</p>}
|
|
</Nav>
|
|
<Component
|
|
addRelation={value => {
|
|
addRelation({ target: { name, value } });
|
|
}}
|
|
id={name}
|
|
isDisabled={!editable}
|
|
isLoading={isLoading}
|
|
isClearable
|
|
mainField={mainField}
|
|
move={moveRelation}
|
|
name={name}
|
|
nextSearch={nextSearch}
|
|
options={options}
|
|
onChange={value => {
|
|
onChange({ target: { name, value: value ? value.value : value } });
|
|
}}
|
|
onInputChange={onInputChange}
|
|
onMenuClose={() => {
|
|
setState(prevState => ({ ...prevState, _q: '', _start: 0 }));
|
|
}}
|
|
onMenuScrollToBottom={onMenuScrollToBottom}
|
|
onRemove={onRemove}
|
|
placeholder={
|
|
isEmpty(placeholder) ? (
|
|
<FormattedMessage id={`${pluginId}.containers.Edit.addAnItem`} />
|
|
) : (
|
|
placeholder
|
|
)
|
|
}
|
|
source={source}
|
|
targetModel={targetModel}
|
|
value={value}
|
|
/>
|
|
<div style={{ marginBottom: 18 }} />
|
|
</Wrapper>
|
|
);
|
|
}
|
|
|
|
SelectWrapper.defaultProps = {
|
|
editable: true,
|
|
description: '',
|
|
label: '',
|
|
plugin: '',
|
|
placeholder: '',
|
|
value: null,
|
|
};
|
|
|
|
SelectWrapper.propTypes = {
|
|
editable: PropTypes.bool,
|
|
description: PropTypes.string,
|
|
label: PropTypes.string,
|
|
mainField: PropTypes.string.isRequired,
|
|
name: PropTypes.string.isRequired,
|
|
placeholder: PropTypes.string,
|
|
plugin: PropTypes.string,
|
|
relationType: PropTypes.string.isRequired,
|
|
targetModel: PropTypes.string.isRequired,
|
|
value: PropTypes.oneOfType([PropTypes.object, PropTypes.array]),
|
|
};
|
|
|
|
export default memo(SelectWrapper);
|