mirror of
https://github.com/strapi/strapi.git
synced 2025-12-27 15:13:21 +00:00
Fix request tasks for selects
This commit is contained in:
parent
771b8ab9b5
commit
c74cbd9126
@ -39,7 +39,7 @@ function InputDate(props) {
|
||||
'form-control',
|
||||
styles.inputDate,
|
||||
!props.deactivateErrorHighlight && props.error && 'is-invalid',
|
||||
!isEmpty(props.className) && props.className,
|
||||
!isEmpty(props.className) && props.className
|
||||
),
|
||||
disabled: props.disabled,
|
||||
id: props.name,
|
||||
|
||||
@ -59,7 +59,7 @@ function Row({ goTo, isBulkable, row, headers }) {
|
||||
return (
|
||||
<>
|
||||
{isBulkable && (
|
||||
<td key="i">
|
||||
<td key="i" onClick={e => e.stopPropagation()}>
|
||||
<CustomInputCheckbox
|
||||
name={row.id}
|
||||
onChange={onChangeBulk}
|
||||
|
||||
@ -37,12 +37,11 @@ function Input({ type, ...rest }) {
|
||||
const style = { width: '210px', paddingTop: '4px' };
|
||||
const styles =
|
||||
type === 'boolean' ? { minWidth: '100px', maxWidth: '200px' } : style;
|
||||
|
||||
const wrapperStyle = type == 'boolean' ? { marginRight: '20px' } : {};
|
||||
|
||||
return (
|
||||
<InputWrapperDate type={type || 'text'} style={wrapperStyle}>
|
||||
<Component {...rest} style={styles} />
|
||||
<Component {...rest} style={styles} autoComplete="off" />
|
||||
</InputWrapperDate>
|
||||
);
|
||||
}
|
||||
|
||||
@ -13,13 +13,7 @@ import 'codemirror/addon/lint/javascript-lint';
|
||||
import 'codemirror/addon/edit/closebrackets';
|
||||
import 'codemirror/addon/selection/mark-selection';
|
||||
import 'codemirror/lib/codemirror.css';
|
||||
import 'codemirror/theme/liquibyte.css';
|
||||
import 'codemirror/theme/xq-dark.css';
|
||||
import 'codemirror/theme/3024-day.css';
|
||||
import 'codemirror/theme/3024-night.css';
|
||||
import 'codemirror/theme/blackboard.css';
|
||||
import 'codemirror/theme/monokai.css';
|
||||
import 'codemirror/theme/cobalt.css';
|
||||
|
||||
import { isEmpty, isObject, trimStart } from 'lodash';
|
||||
import jsonlint from './jsonlint';
|
||||
@ -28,16 +22,7 @@ import styles from './styles.scss';
|
||||
const WAIT = 600;
|
||||
const stringify = JSON.stringify;
|
||||
const parse = JSON.parse;
|
||||
const DEFAULT_THEME = 'monokai';
|
||||
const THEMES = [
|
||||
'blackboard',
|
||||
'cobalt',
|
||||
'monokai',
|
||||
'3024-day',
|
||||
'3024-night',
|
||||
'liquibyte',
|
||||
'xq-dark',
|
||||
];
|
||||
const DEFAULT_THEME = '3024-night';
|
||||
|
||||
class InputJSON extends React.Component {
|
||||
constructor(props) {
|
||||
@ -94,8 +79,6 @@ class InputJSON extends React.Component {
|
||||
|
||||
setSize = () => this.codeMirror.setSize('100%', 'auto');
|
||||
|
||||
setTheme = theme => this.codeMirror.setOption('theme', theme);
|
||||
|
||||
getContentAtLine = line => this.codeMirror.getLine(line);
|
||||
|
||||
getEditorOption = opt => this.codeMirror.getOption(opt);
|
||||
@ -104,7 +87,6 @@ class InputJSON extends React.Component {
|
||||
|
||||
markSelection = ({ message }) => {
|
||||
let line = parseInt(message.split(':')[0].split('line ')[1], 10) - 1;
|
||||
|
||||
let content = this.getContentAtLine(line);
|
||||
|
||||
if (content === '{') {
|
||||
@ -196,17 +178,6 @@ class InputJSON extends React.Component {
|
||||
id={this.props.name}
|
||||
defaultValue=""
|
||||
/>
|
||||
<select
|
||||
className={styles.select}
|
||||
onChange={({ target }) => this.setTheme(target.value)}
|
||||
defaultValue={DEFAULT_THEME}
|
||||
>
|
||||
{THEMES.sort().map(theme => (
|
||||
<option key={theme} value={theme}>
|
||||
{theme}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -49,6 +49,7 @@ function ListItem({
|
||||
}, [preview]);
|
||||
|
||||
const opacity = isDragging ? 0.2 : 1;
|
||||
|
||||
return (
|
||||
<Li ref={node => drag(drop(node))} style={{ opacity }}>
|
||||
<Relation mainField={mainField} onRemove={onRemove} data={data} to={to} />
|
||||
|
||||
@ -1,151 +0,0 @@
|
||||
/**
|
||||
*
|
||||
* SortableItem
|
||||
*
|
||||
*/
|
||||
|
||||
/* eslint-disable react/no-find-dom-node */
|
||||
import React from 'react';
|
||||
import { findDOMNode } from 'react-dom';
|
||||
import { DragSource, DropTarget } from 'react-dnd';
|
||||
import { getEmptyImage } from 'react-dnd-html5-backend';
|
||||
import PropTypes from 'prop-types';
|
||||
import { flow, get } from 'lodash';
|
||||
import cn from 'classnames';
|
||||
|
||||
import ItemTypes from '../../utils/ItemTypes';
|
||||
|
||||
import SelectManyDraggedItem from '../SelectManyDraggedItem';
|
||||
|
||||
import styles from './styles.scss';
|
||||
|
||||
const sortableItemSource = {
|
||||
beginDrag: props => {
|
||||
return {
|
||||
id: get(props, ['item', 'value', 'id' ]) || get(props, ['item', 'value', '_id'], ''),
|
||||
index: props.index,
|
||||
data: props.item,
|
||||
};
|
||||
},
|
||||
endDrag: props => {
|
||||
props.moveAttrEnd();
|
||||
return {};
|
||||
},
|
||||
};
|
||||
|
||||
const sortableItemTarget = {
|
||||
hover: (props, monitor, component) => {
|
||||
const dragIndex = monitor.getItem().index;
|
||||
const hoverIndex = props.index;
|
||||
|
||||
// Don't replace items with themselves
|
||||
if (dragIndex === hoverIndex) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Determine rectangle on screen
|
||||
const hoverBoundingRect = findDOMNode(component).getBoundingClientRect();
|
||||
|
||||
// Get vertical middle
|
||||
const hoverMiddleY = (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2;
|
||||
|
||||
// Determine mouse position
|
||||
const clientOffset = monitor.getClientOffset();
|
||||
|
||||
// Get pixels to the top
|
||||
const hoverClientY = clientOffset.y - hoverBoundingRect.top;
|
||||
|
||||
// Only perform the move when the mouse has crossed half of the items height
|
||||
// When dragging downwards, only move when the cursor is below 50%
|
||||
// When dragging upwards, only move when the cursor is above 50%
|
||||
|
||||
// Dragging downwards
|
||||
if (dragIndex < hoverIndex && hoverClientY < hoverMiddleY) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Dragging upwards
|
||||
if (dragIndex > hoverIndex && hoverClientY > hoverMiddleY) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Time to actually perform the action
|
||||
|
||||
props.moveAttr(dragIndex, hoverIndex, props.keys);
|
||||
|
||||
// Note: we're mutating the monitor item here!
|
||||
// Generally it's better to avoid mutations,
|
||||
// but it's good here for the sake of performance
|
||||
// to avoid expensive index searches.
|
||||
monitor.getItem().index = hoverIndex;
|
||||
|
||||
},
|
||||
};
|
||||
|
||||
class SortableItem extends React.Component {
|
||||
componentDidMount() {
|
||||
// Use empty image as a drag preview so browsers don't draw it
|
||||
// and we can draw whatever we want on the custom drag layer instead.
|
||||
this.props.connectDragPreview(getEmptyImage(), {
|
||||
// IE fallback: specify that we'd rather screenshot the node
|
||||
// when it already knows it's being dragged so we can hide it with CSS.
|
||||
// Removginv the fallabck makes it handle variable height elements
|
||||
// captureDraggingState: true,
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
connectDragSource,
|
||||
connectDropTarget,
|
||||
index,
|
||||
item,
|
||||
isDragging,
|
||||
isDraggingSibling,
|
||||
onClick,
|
||||
onRemove,
|
||||
} = this.props;
|
||||
const opacity = isDragging ? 0.2 : 1;
|
||||
|
||||
return (
|
||||
connectDragSource(
|
||||
connectDropTarget(
|
||||
<li
|
||||
className={cn(styles.sortableListItem, !isDraggingSibling && styles.sortableListItemHover)}
|
||||
style={{ opacity }}
|
||||
>
|
||||
<SelectManyDraggedItem index={index} item={item} onClick={onClick} onRemove={onRemove} />
|
||||
</li>
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const withDropTarget = DropTarget(ItemTypes.SORTABLEITEM, sortableItemTarget, connect => ({
|
||||
connectDropTarget: connect.dropTarget(),
|
||||
}));
|
||||
|
||||
const withDragSource = DragSource(ItemTypes.SORTABLEITEM, sortableItemSource, (connect, monitor) => ({
|
||||
connectDragPreview: connect.dragPreview(),
|
||||
connectDragSource: connect.dragSource(),
|
||||
isDragging: monitor.isDragging(),
|
||||
}));
|
||||
|
||||
SortableItem.defaultProps = {
|
||||
isDraggingSibling: false,
|
||||
};
|
||||
|
||||
SortableItem.propTypes = {
|
||||
connectDragPreview: PropTypes.func.isRequired,
|
||||
connectDragSource: PropTypes.func.isRequired,
|
||||
connectDropTarget: PropTypes.func.isRequired,
|
||||
index: PropTypes.number.isRequired,
|
||||
isDragging: PropTypes.bool.isRequired,
|
||||
isDraggingSibling: PropTypes.bool,
|
||||
item: PropTypes.object.isRequired,
|
||||
onClick: PropTypes.func.isRequired,
|
||||
onRemove: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default flow([withDropTarget, withDragSource])(SortableItem);
|
||||
@ -1,49 +0,0 @@
|
||||
/**
|
||||
*
|
||||
* SortableList
|
||||
*
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import cn from 'classnames';
|
||||
// import { SortableContainer } from 'react-sortable-hoc';
|
||||
import SortableItem from './SortableItem';
|
||||
// CSS.
|
||||
import styles from './styles.scss';
|
||||
|
||||
const SortableList = ({ items, isDraggingSibling, keys, moveAttr, moveAttrEnd, name, onClick, onRemove }) => {
|
||||
return (
|
||||
<div className={cn(styles.sortableList)}>
|
||||
<ul id={`sortableListOf${name}`}>
|
||||
{items.map((item, index) => (
|
||||
<SortableItem
|
||||
isDraggingSibling={isDraggingSibling}
|
||||
key={item.value.id || item.value._id || `item-${index}`}
|
||||
keys={keys}
|
||||
index={index}
|
||||
item={item}
|
||||
moveAttr={moveAttr}
|
||||
moveAttrEnd={moveAttrEnd}
|
||||
onRemove={onRemove}
|
||||
onClick={onClick}
|
||||
/>
|
||||
))}
|
||||
</ul>
|
||||
{items.length > 4 && <div className={styles.sortableListLong} />}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
SortableList.propTypes = {
|
||||
isDraggingSibling: PropTypes.bool.isRequired,
|
||||
items: PropTypes.array.isRequired,
|
||||
keys: PropTypes.string.isRequired,
|
||||
moveAttr: PropTypes.func.isRequired,
|
||||
moveAttrEnd: PropTypes.func.isRequired,
|
||||
name: PropTypes.string.isRequired,
|
||||
onClick: PropTypes.func.isRequired,
|
||||
onRemove: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default SortableList;
|
||||
@ -1,267 +0,0 @@
|
||||
/**
|
||||
*
|
||||
* 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,188 +0,0 @@
|
||||
.selectMany { /* stylelint-disable */
|
||||
padding-bottom: 20px;
|
||||
margin-bottom: 0px;
|
||||
|
||||
label{
|
||||
font-size: 1.3rem;
|
||||
font-weight: 500;
|
||||
text-transform: capitalize;
|
||||
margin-top: 3px;
|
||||
|
||||
> span {
|
||||
font-weight: 400;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
}
|
||||
|
||||
label + div{
|
||||
margin: 3px 0 26px;
|
||||
|
||||
&:focus{
|
||||
outline: none;
|
||||
}
|
||||
|
||||
>div{
|
||||
box-shadow: none !important;
|
||||
border-color: #E3E9F3 !important;
|
||||
|
||||
>span:last-of-type{
|
||||
span{
|
||||
border-color: #B3B5B9 transparent transparent;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.selectManyUpdate{
|
||||
padding-bottom: 15px !important;
|
||||
}
|
||||
|
||||
.select{
|
||||
margin-bottom: 0px !important;
|
||||
}
|
||||
|
||||
.sortableList {
|
||||
overflow: hidden;
|
||||
max-height: 116px;
|
||||
margin-bottom: -9px;
|
||||
|
||||
> ul {
|
||||
margin: 4px -20px 0;
|
||||
padding: 0 20px !important;
|
||||
list-style: none !important;
|
||||
overflow: auto;
|
||||
max-height: 110px;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.sortableListLong {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
height: 0px;
|
||||
|
||||
&:after {
|
||||
position: absolute;
|
||||
top: -15px;
|
||||
left: -5px;
|
||||
content: '';
|
||||
display: inline-block;
|
||||
width: calc(100% + 10px);
|
||||
height: 1px;
|
||||
margin-bottom: -25px;
|
||||
box-shadow: 0px -2px 4px 0px rgba(227, 233, 243, .5);
|
||||
}
|
||||
}
|
||||
|
||||
.sortableListItemHover {
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.sortableListItem {
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
align-content: center;
|
||||
justify-content: space-between;
|
||||
height: 27px;
|
||||
background-color: transparent !important;
|
||||
|
||||
&:active{
|
||||
.dragHandle{
|
||||
// cursor: pointer;
|
||||
|
||||
> span {
|
||||
background: #AED4FB;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dragHandle{
|
||||
outline: none;
|
||||
text-decoration: none;
|
||||
margin-top: -1px;
|
||||
|
||||
> span {
|
||||
vertical-align: middle;
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 6px;
|
||||
height: 1px;
|
||||
padding: 0px !important;
|
||||
background: #B3B5B9;
|
||||
overflow: visible !important;
|
||||
transition: background .25s ease-out;
|
||||
|
||||
&:before, &:after{
|
||||
content: '';
|
||||
display: inline-block;
|
||||
width: 6px;
|
||||
height: 1px;
|
||||
background: inherit;
|
||||
}
|
||||
|
||||
&:before{
|
||||
position: absolute;
|
||||
top: -2px;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
&:after{
|
||||
position: absolute;
|
||||
bottom: -2px;
|
||||
left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
> div {
|
||||
width: 90%;
|
||||
span {
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
&:first-of-type{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
transition: color .25s ease-out;
|
||||
|
||||
&:hover{
|
||||
.dragHandle{
|
||||
> span {
|
||||
background: #007EFF;
|
||||
}
|
||||
}
|
||||
|
||||
color: #007EFF;
|
||||
}
|
||||
|
||||
span {
|
||||
&:last-of-type{
|
||||
padding-left: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:last-of-type{
|
||||
display: inline-block;
|
||||
height: 100%;
|
||||
padding-right: 0px;
|
||||
line-height: 27px;
|
||||
text-align: right;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
img{
|
||||
display: inline-block;
|
||||
height: 14px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,52 +0,0 @@
|
||||
/**
|
||||
*
|
||||
* Content
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import IconRemove from '../../assets/images/icon_remove.svg';
|
||||
import styles from '../SelectMany/styles.scss';
|
||||
|
||||
function Content({ index, item, onClick, onRemove }) {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<div>
|
||||
<div className={styles.dragHandle}>
|
||||
<span />
|
||||
</div>
|
||||
<FormattedMessage id="content-manager.containers.Edit.clickToJump">
|
||||
{title => (
|
||||
<span onClick={() => onClick(item)} title={title}>
|
||||
{item.label}
|
||||
</span>
|
||||
)}
|
||||
</FormattedMessage>
|
||||
</div>
|
||||
<div className={styles.selectManyDraggedItemActions}>
|
||||
<img
|
||||
src={IconRemove}
|
||||
alt="Remove Icon"
|
||||
onClick={() => onRemove(index)}
|
||||
/>
|
||||
</div>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
Content.defaultProps = {
|
||||
index: 0,
|
||||
onClick: () => {},
|
||||
onRemove: () => {},
|
||||
};
|
||||
|
||||
Content.propTypes = {
|
||||
index: PropTypes.number,
|
||||
item: PropTypes.object.isRequired,
|
||||
onClick: PropTypes.func,
|
||||
onRemove: PropTypes.func,
|
||||
};
|
||||
|
||||
export default Content;
|
||||
@ -1,38 +0,0 @@
|
||||
/**
|
||||
*
|
||||
* SelectManyDraggedItem
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import styles from '../SelectMany/styles.scss';
|
||||
import Content from './Content';
|
||||
|
||||
function SelectManyDraggedItem(props) {
|
||||
if (props.withLiWrapper) {
|
||||
return (
|
||||
<li className={styles.sortableListItem} style={{ padding: '0 2px' }}>
|
||||
<Content {...props} />
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
return <Content {...props} />;
|
||||
}
|
||||
|
||||
SelectManyDraggedItem.defaultProps = {
|
||||
index: 0,
|
||||
onClick: () => {},
|
||||
onRemove: () => {},
|
||||
withLiWrapper: false,
|
||||
};
|
||||
|
||||
SelectManyDraggedItem.propTypes = {
|
||||
index: PropTypes.number,
|
||||
item: PropTypes.object.isRequired,
|
||||
onClick: PropTypes.func,
|
||||
onRemove: PropTypes.func,
|
||||
withLiWrapper: PropTypes.bool,
|
||||
};
|
||||
|
||||
export default SelectManyDraggedItem;
|
||||
@ -1,228 +0,0 @@
|
||||
/**
|
||||
*
|
||||
* SelectOne
|
||||
*
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import Select from 'react-select';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import {
|
||||
cloneDeep,
|
||||
map,
|
||||
includes,
|
||||
isArray,
|
||||
isNull,
|
||||
isUndefined,
|
||||
isFunction,
|
||||
get,
|
||||
findIndex,
|
||||
} from 'lodash';
|
||||
|
||||
import { request, templateObject } from 'strapi-helper-plugin';
|
||||
|
||||
import styles from './styles.scss';
|
||||
|
||||
class SelectOne extends React.Component {
|
||||
// eslint-disable-line react/prefer-stateless-function
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
isLoading: true,
|
||||
options: [],
|
||||
toSkip: 0,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.getOptions('');
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
if (prevState.toSkip !== this.state.toSkip) {
|
||||
this.getOptions('');
|
||||
}
|
||||
}
|
||||
|
||||
getOptions = query => {
|
||||
const params = {
|
||||
_limit: 20,
|
||||
_start: this.state.toSkip,
|
||||
source: this.props.relation.plugin || 'content-manager',
|
||||
};
|
||||
|
||||
// Set `query` parameter if necessary
|
||||
if (query) {
|
||||
delete params._limit;
|
||||
delete params._start;
|
||||
params[`${this.props.relation.displayedAttribute}_contains`] = query;
|
||||
}
|
||||
|
||||
// Request URL
|
||||
const requestUrlSuffix =
|
||||
query && get(this.props.record, [this.props.relation.alias])
|
||||
? get(this.props.record, [this.props.relation.alias])
|
||||
: '';
|
||||
const requestUrl = `/content-manager/explorer/${this.props.relation.model ||
|
||||
this.props.relation.collection}/${requestUrlSuffix}`;
|
||||
|
||||
// Call our request helper (see 'utils/request')
|
||||
return request(requestUrl, {
|
||||
method: 'GET',
|
||||
params,
|
||||
})
|
||||
.then(response => {
|
||||
const options = isArray(response)
|
||||
? map(response, item => ({
|
||||
value: item,
|
||||
label: templateObject(
|
||||
{ mainField: this.props.relation.displayedAttribute },
|
||||
item,
|
||||
).mainField,
|
||||
}))
|
||||
: [
|
||||
{
|
||||
value: response,
|
||||
label: templateObject(
|
||||
{ mainField: this.props.relation.displayedAttribute },
|
||||
response,
|
||||
).mainField,
|
||||
},
|
||||
];
|
||||
|
||||
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',
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
handleChange = value => {
|
||||
const target = {
|
||||
name: `record.${this.props.relation.alias}`,
|
||||
value,
|
||||
type: 'select',
|
||||
};
|
||||
|
||||
this.props.setRecordAttribute({ target });
|
||||
};
|
||||
|
||||
handleBottomScroll = () => {
|
||||
this.setState(prevState => {
|
||||
return {
|
||||
toSkip: prevState.toSkip + 1,
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
// 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,
|
||||
});
|
||||
};
|
||||
|
||||
handleInputChange = value => {
|
||||
const clonedOptions = this.state.options;
|
||||
const filteredValues = clonedOptions.filter(data =>
|
||||
includes(data.label, value),
|
||||
);
|
||||
|
||||
if (filteredValues.length === 0) {
|
||||
return this.getOptions(value);
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const description = this.props.relation.description ? (
|
||||
<p>{this.props.relation.description}</p>
|
||||
) : (
|
||||
''
|
||||
);
|
||||
|
||||
const value = get(this.props.record, this.props.relation.alias);
|
||||
const excludeModel = ['role', 'permission', 'file'].includes(
|
||||
this.props.relation.model || this.props.relation.collection,
|
||||
); // Temporary.
|
||||
const entryLink =
|
||||
isNull(value) || isUndefined(value) || excludeModel ? (
|
||||
''
|
||||
) : (
|
||||
<FormattedMessage id="content-manager.containers.Edit.clickToJump">
|
||||
{title => (
|
||||
<a onClick={() => this.handleClick({ value })} title={title}>
|
||||
<FormattedMessage id="content-manager.containers.Edit.seeDetails" />
|
||||
</a>
|
||||
)}
|
||||
</FormattedMessage>
|
||||
);
|
||||
|
||||
/* eslint-disable jsx-a11y/label-has-for */
|
||||
return (
|
||||
<div className={`form-group ${styles.selectOne}`}>
|
||||
<nav className={styles.headline}>
|
||||
<label htmlFor={this.props.relation.alias}>
|
||||
{this.props.relation.alias}
|
||||
</label>
|
||||
{entryLink}
|
||||
</nav>
|
||||
{description}
|
||||
<Select
|
||||
onChange={this.handleChange}
|
||||
options={this.state.options}
|
||||
id={this.props.relation.alias}
|
||||
isLoading={this.state.isLoading}
|
||||
onMenuScrollToBottom={this.handleBottomScroll}
|
||||
onInputChange={this.handleInputChange}
|
||||
onSelectResetsInput={false}
|
||||
simpleValue
|
||||
value={
|
||||
isNull(value) || isUndefined(value)
|
||||
? null
|
||||
: {
|
||||
value: isFunction(value.toJS) ? value.toJS() : value,
|
||||
label:
|
||||
templateObject(
|
||||
{ mainField: this.props.relation.displayedAttribute },
|
||||
isFunction(value.toJS) ? value.toJS() : value,
|
||||
).mainField ||
|
||||
(isFunction(value.toJS)
|
||||
? get(value.toJS(), 'id')
|
||||
: get(value, 'id')),
|
||||
}
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
/* eslint-disable jsx-a11y/label-has-for */
|
||||
}
|
||||
}
|
||||
|
||||
SelectOne.propTypes = {
|
||||
onRedirect: PropTypes.func.isRequired,
|
||||
record: PropTypes.oneOfType([PropTypes.object, PropTypes.bool]).isRequired,
|
||||
relation: PropTypes.object.isRequired,
|
||||
setRecordAttribute: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default SelectOne;
|
||||
@ -1,48 +0,0 @@
|
||||
.selectOne { /* stylelint-disable */
|
||||
position: relative;
|
||||
|
||||
label{
|
||||
font-size: 1.3rem;
|
||||
font-weight: 500;
|
||||
text-transform: capitalize;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.headline{
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
a{
|
||||
color: #007EFF !important;
|
||||
font-size: 1.3rem;
|
||||
padding-top: 3px;
|
||||
|
||||
&:hover{
|
||||
text-decoration: underline !important;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -41,9 +41,9 @@ function SelectWrapper({
|
||||
const startRef = useRef();
|
||||
startRef.current = state._start;
|
||||
|
||||
ref.current = async (uid, params = state) => {
|
||||
ref.current = async (signal, params = state) => {
|
||||
try {
|
||||
const requestUrl = `/${pluginId}/explorer/${uid}`;
|
||||
const requestUrl = `/${pluginId}/explorer/${targetModel}`;
|
||||
|
||||
if (isEmpty(params._q)) {
|
||||
delete params._q;
|
||||
@ -52,6 +52,7 @@ function SelectWrapper({
|
||||
const data = await request(requestUrl, {
|
||||
method: 'GET',
|
||||
params: params,
|
||||
signal,
|
||||
});
|
||||
const formattedData = data.map(obj => {
|
||||
return { value: obj, label: obj[mainField] };
|
||||
@ -84,10 +85,14 @@ function SelectWrapper({
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
ref.current(targetModel);
|
||||
const abortController = new AbortController();
|
||||
const { signal } = abortController;
|
||||
ref.current(signal);
|
||||
|
||||
return () => {};
|
||||
}, [ref, targetModel, state._start, state._q]);
|
||||
return () => {
|
||||
abortController.abort();
|
||||
};
|
||||
}, [ref]);
|
||||
|
||||
const onInputChange = inputValue => {
|
||||
setState(prevState => {
|
||||
@ -98,11 +103,15 @@ function SelectWrapper({
|
||||
return { ...prevState, _q: inputValue };
|
||||
});
|
||||
|
||||
ref.current();
|
||||
|
||||
return inputValue;
|
||||
};
|
||||
|
||||
const onMenuScrollToBottom = () => {
|
||||
setState(prevState => ({ ...prevState, _start: prevState._start + 1 }));
|
||||
|
||||
ref.current();
|
||||
};
|
||||
const isSingle = [
|
||||
'oneWay',
|
||||
|
||||
@ -32,9 +32,9 @@ function settingViewModelReducer(state = initialState, action) {
|
||||
);
|
||||
case GET_DATA_SUCCEEDED:
|
||||
return state
|
||||
.update('initialData', () => fromJS(action.layout))
|
||||
.update('initialData', () => fromJS(action.layout || {}))
|
||||
.update('isLoading', () => false)
|
||||
.update('modifiedData', () => fromJS(action.layout));
|
||||
.update('modifiedData', () => fromJS(action.layout || {}));
|
||||
case MOVE_FIELD_LIST:
|
||||
return state
|
||||
.updateIn(['modifiedData', 'layouts', 'list'], list => {
|
||||
|
||||
@ -18,7 +18,7 @@ export function getDataSucceeded(generalSettings, groups, models) {
|
||||
type: GET_DATA_SUCCEEDED,
|
||||
generalSettings,
|
||||
groups,
|
||||
models,
|
||||
models: models.filter(model => model.isDisplayed === true),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user