2019-11-05 17:50:22 +01:00
|
|
|
import React, { useEffect, useRef } from 'react';
|
2019-11-05 15:12:48 +01:00
|
|
|
import PropTypes from 'prop-types';
|
|
|
|
import { get } from 'lodash';
|
|
|
|
import { Collapse } from 'reactstrap';
|
2019-11-05 17:50:22 +01:00
|
|
|
import { useDrag, useDrop } from 'react-dnd';
|
|
|
|
import { getEmptyImage } from 'react-dnd-html5-backend';
|
2019-11-05 15:12:48 +01:00
|
|
|
import useDataManager from '../../hooks/useDataManager';
|
2019-11-06 13:02:48 +01:00
|
|
|
import useEditView from '../../hooks/useEditView';
|
2019-11-05 17:50:22 +01:00
|
|
|
import ItemTypes from '../../utils/ItemTypes';
|
2019-11-05 15:12:48 +01:00
|
|
|
import Inputs from '../Inputs';
|
|
|
|
import FieldComponent from '../FieldComponent';
|
|
|
|
import Banner from './Banner';
|
|
|
|
import FormWrapper from './FormWrapper';
|
|
|
|
|
2019-11-06 13:02:48 +01:00
|
|
|
// Issues:
|
|
|
|
// https://github.com/react-dnd/react-dnd/issues/1368
|
|
|
|
// https://github.com/frontend-collective/react-sortable-tree/issues/490
|
|
|
|
|
2019-11-05 15:12:48 +01:00
|
|
|
const DraggedItem = ({
|
|
|
|
componentFieldName,
|
2019-11-05 18:38:48 +01:00
|
|
|
doesPreviousFieldContainErrorsAndIsOpen,
|
2019-11-05 15:12:48 +01:00
|
|
|
fields,
|
2019-11-05 18:38:48 +01:00
|
|
|
hasErrors,
|
2019-11-08 16:17:20 +01:00
|
|
|
hasMinError,
|
|
|
|
isFirst,
|
2019-11-05 15:12:48 +01:00
|
|
|
isOpen,
|
2019-11-05 17:50:22 +01:00
|
|
|
moveCollapse,
|
2019-11-05 15:12:48 +01:00
|
|
|
onClickToggle,
|
2019-11-05 15:36:22 +01:00
|
|
|
removeCollapse,
|
2019-11-05 15:12:48 +01:00
|
|
|
schema,
|
2019-11-05 17:50:22 +01:00
|
|
|
toggleCollapses,
|
2019-11-05 15:12:48 +01:00
|
|
|
}) => {
|
2019-11-05 17:50:22 +01:00
|
|
|
const {
|
2019-11-08 16:17:20 +01:00
|
|
|
checkFormErrors,
|
2019-11-05 17:50:22 +01:00
|
|
|
modifiedData,
|
|
|
|
moveComponentField,
|
|
|
|
removeRepeatableField,
|
2020-01-20 15:15:35 +01:00
|
|
|
triggerFormValidation,
|
2019-11-05 17:50:22 +01:00
|
|
|
} = useDataManager();
|
2019-11-06 13:02:48 +01:00
|
|
|
const { setIsDraggingComponent, unsetIsDraggingComponent } = useEditView();
|
2019-11-05 15:12:48 +01:00
|
|
|
const mainField = get(schema, ['settings', 'mainField'], 'id');
|
|
|
|
const displayedValue = get(
|
|
|
|
modifiedData,
|
|
|
|
[...componentFieldName.split('.'), mainField],
|
|
|
|
null
|
|
|
|
);
|
2019-11-05 17:50:22 +01:00
|
|
|
const dragRef = useRef(null);
|
|
|
|
const dropRef = useRef(null);
|
|
|
|
|
|
|
|
const [, drop] = useDrop({
|
|
|
|
accept: ItemTypes.COMPONENT,
|
2019-11-06 13:02:48 +01:00
|
|
|
canDrop() {
|
|
|
|
return false;
|
|
|
|
},
|
2019-11-05 17:50:22 +01:00
|
|
|
hover(item, monitor) {
|
|
|
|
if (!dropRef.current) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const dragPath = item.originalPath;
|
|
|
|
const hoverPath = componentFieldName;
|
|
|
|
const fullPathToComponentArray = dragPath.split('.');
|
|
|
|
const dragIndexString = fullPathToComponentArray
|
|
|
|
.slice()
|
|
|
|
.splice(-1)
|
|
|
|
.join('');
|
|
|
|
const hoverIndexString = hoverPath
|
|
|
|
.split('.')
|
|
|
|
.splice(-1)
|
|
|
|
.join('');
|
|
|
|
const pathToComponentArray = fullPathToComponentArray.slice(
|
|
|
|
0,
|
|
|
|
fullPathToComponentArray.length - 1
|
|
|
|
);
|
|
|
|
const dragIndex = parseInt(dragIndexString, 10);
|
|
|
|
const hoverIndex = parseInt(hoverIndexString, 10);
|
|
|
|
|
|
|
|
// Don't replace items with themselves
|
2019-11-06 13:02:48 +01:00
|
|
|
if (dragIndex === hoverIndex) {
|
2019-11-05 17:50:22 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Determine rectangle on screen
|
|
|
|
const hoverBoundingRect = dropRef.current.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 in the data
|
|
|
|
moveComponentField(pathToComponentArray, dragIndex, hoverIndex);
|
|
|
|
// Time to actually perform the action in the synchronized collapses
|
|
|
|
moveCollapse(dragIndex, hoverIndex);
|
|
|
|
// 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.
|
|
|
|
item.originalPath = hoverPath;
|
|
|
|
|
|
|
|
return;
|
|
|
|
},
|
|
|
|
});
|
|
|
|
const [{ isDragging }, drag, preview] = useDrag({
|
|
|
|
item: {
|
|
|
|
type: ItemTypes.COMPONENT,
|
|
|
|
displayedValue,
|
|
|
|
originalPath: componentFieldName,
|
|
|
|
},
|
|
|
|
begin: () => {
|
|
|
|
// Close all collapses
|
|
|
|
toggleCollapses(-1);
|
2019-11-06 13:02:48 +01:00
|
|
|
// Prevent the relations select from firing requests
|
|
|
|
setIsDraggingComponent();
|
|
|
|
},
|
|
|
|
end: () => {
|
|
|
|
// Enable the relations select to fire requests
|
|
|
|
unsetIsDraggingComponent();
|
2020-01-20 15:15:35 +01:00
|
|
|
// Update the errors
|
|
|
|
triggerFormValidation();
|
2019-11-05 17:50:22 +01:00
|
|
|
},
|
|
|
|
collect: monitor => ({
|
|
|
|
isDragging: monitor.isDragging(),
|
|
|
|
}),
|
|
|
|
});
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
preview(getEmptyImage(), { captureDraggingState: false });
|
|
|
|
}, [preview]);
|
|
|
|
|
2019-11-05 15:12:48 +01:00
|
|
|
const getField = fieldName =>
|
|
|
|
get(schema, ['schema', 'attributes', fieldName], {});
|
|
|
|
const getMeta = fieldName =>
|
|
|
|
get(schema, ['metadatas', fieldName, 'edit'], {});
|
|
|
|
|
2019-11-05 17:50:22 +01:00
|
|
|
// Create the refs
|
|
|
|
// We need 1 for the drop target
|
|
|
|
// 1 for the drag target
|
|
|
|
const refs = {
|
|
|
|
dragRef: drag(dragRef),
|
|
|
|
dropRef: drop(dropRef),
|
|
|
|
};
|
|
|
|
|
2019-11-05 15:12:48 +01:00
|
|
|
return (
|
|
|
|
<>
|
|
|
|
<Banner
|
2019-11-05 15:36:22 +01:00
|
|
|
componentFieldName={componentFieldName}
|
2019-11-05 18:38:48 +01:00
|
|
|
hasErrors={hasErrors}
|
2019-11-08 16:17:20 +01:00
|
|
|
hasMinError={hasMinError}
|
|
|
|
isFirst={isFirst}
|
2019-11-05 15:12:48 +01:00
|
|
|
displayedValue={displayedValue}
|
2019-11-05 18:38:48 +01:00
|
|
|
doesPreviousFieldContainErrorsAndIsOpen={
|
|
|
|
doesPreviousFieldContainErrorsAndIsOpen
|
|
|
|
}
|
2019-11-05 17:50:22 +01:00
|
|
|
isDragging={isDragging}
|
2019-11-05 15:12:48 +01:00
|
|
|
isOpen={isOpen}
|
|
|
|
onClickToggle={onClickToggle}
|
2019-11-05 15:36:22 +01:00
|
|
|
onClickRemove={() => {
|
|
|
|
removeRepeatableField(componentFieldName);
|
|
|
|
removeCollapse();
|
|
|
|
}}
|
2019-11-05 17:50:22 +01:00
|
|
|
ref={refs}
|
2019-11-05 15:12:48 +01:00
|
|
|
/>
|
|
|
|
<Collapse isOpen={isOpen} style={{ backgroundColor: '#FAFAFB' }}>
|
2020-01-20 15:15:35 +01:00
|
|
|
{!isDragging && (
|
|
|
|
<FormWrapper hasErrors={hasErrors} isOpen={isOpen}>
|
|
|
|
{fields.map((fieldRow, key) => {
|
|
|
|
return (
|
|
|
|
<div className="row" key={key}>
|
|
|
|
{fieldRow.map(field => {
|
|
|
|
const currentField = getField(field.name);
|
|
|
|
const isComponent =
|
|
|
|
get(currentField, 'type', '') === 'component';
|
|
|
|
const keys = `${componentFieldName}.${field.name}`;
|
|
|
|
|
|
|
|
if (isComponent) {
|
|
|
|
const componentUid = currentField.component;
|
|
|
|
const metas = getMeta(field.name);
|
|
|
|
|
|
|
|
return (
|
|
|
|
<FieldComponent
|
|
|
|
componentUid={componentUid}
|
|
|
|
isRepeatable={currentField.repeatable}
|
|
|
|
key={field.name}
|
|
|
|
label={metas.label}
|
|
|
|
isSub
|
|
|
|
name={keys}
|
|
|
|
max={currentField.max}
|
|
|
|
min={currentField.min}
|
|
|
|
/>
|
|
|
|
);
|
|
|
|
}
|
2019-11-05 15:36:22 +01:00
|
|
|
|
2019-11-05 15:12:48 +01:00
|
|
|
return (
|
2020-01-20 15:15:35 +01:00
|
|
|
<div key={field.name} className={`col-${field.size}`}>
|
|
|
|
<Inputs
|
|
|
|
autoFocus={false}
|
|
|
|
keys={keys}
|
|
|
|
layout={schema}
|
|
|
|
name={field.name}
|
|
|
|
onChange={() => {}}
|
|
|
|
onBlur={hasErrors ? checkFormErrors : null}
|
|
|
|
/>
|
|
|
|
</div>
|
2019-11-05 15:12:48 +01:00
|
|
|
);
|
2020-01-20 15:15:35 +01:00
|
|
|
})}
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
})}
|
|
|
|
</FormWrapper>
|
|
|
|
)}
|
2019-11-05 15:12:48 +01:00
|
|
|
</Collapse>
|
|
|
|
</>
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
DraggedItem.defaultProps = {
|
2019-11-05 18:38:48 +01:00
|
|
|
doesPreviousFieldContainErrorsAndIsOpen: false,
|
2019-11-05 15:12:48 +01:00
|
|
|
fields: [],
|
2019-11-05 18:38:48 +01:00
|
|
|
hasErrors: false,
|
2019-11-08 16:17:20 +01:00
|
|
|
hasMinError: false,
|
|
|
|
isFirst: false,
|
2019-11-05 15:12:48 +01:00
|
|
|
isOpen: false,
|
2019-11-05 17:50:22 +01:00
|
|
|
moveCollapse: () => {},
|
|
|
|
toggleCollapses: () => {},
|
2019-11-05 15:12:48 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
DraggedItem.propTypes = {
|
|
|
|
componentFieldName: PropTypes.string.isRequired,
|
2019-11-05 18:38:48 +01:00
|
|
|
doesPreviousFieldContainErrorsAndIsOpen: PropTypes.bool,
|
2019-11-05 15:12:48 +01:00
|
|
|
fields: PropTypes.array,
|
2019-11-05 18:38:48 +01:00
|
|
|
hasErrors: PropTypes.bool,
|
2019-11-08 16:17:20 +01:00
|
|
|
hasMinError: PropTypes.bool,
|
|
|
|
isFirst: PropTypes.bool,
|
2019-11-05 15:12:48 +01:00
|
|
|
isOpen: PropTypes.bool,
|
2019-11-05 17:50:22 +01:00
|
|
|
moveCollapse: PropTypes.func,
|
2019-11-05 15:12:48 +01:00
|
|
|
onClickToggle: PropTypes.func.isRequired,
|
2019-11-05 15:36:22 +01:00
|
|
|
removeCollapse: PropTypes.func.isRequired,
|
2019-11-05 15:12:48 +01:00
|
|
|
schema: PropTypes.object.isRequired,
|
2019-11-05 17:50:22 +01:00
|
|
|
toggleCollapses: PropTypes.func,
|
2019-11-05 15:12:48 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
export default DraggedItem;
|