Handle reorder full size elements

This commit is contained in:
soupette 2019-07-23 12:43:32 +02:00
parent fab10fea20
commit f12b8f8c87
10 changed files with 296 additions and 53 deletions

View File

@ -3,6 +3,7 @@ import { useDragLayer } from 'react-dnd';
import ItemTypes from '../../utils/itemsTypes';
import RelationItem from '../SelectMany/Relation';
import { Li } from '../SelectMany/components';
import FieldItem from '../FieldItem';
const layerStyles = {
position: 'fixed',
@ -20,7 +21,8 @@ function getItemStyles(initialOffset, currentOffset, mouseOffset) {
}
const { x, y } = mouseOffset;
const transform = `translate(${x - 50}px, ${y - 5}px)`;
// TODO adjust
const transform = `translate(${x}px, ${y}px)`;
return {
transform,
@ -53,6 +55,8 @@ const CustomDragLayer = () => {
<RelationItem data={item.data} mainField={item.mainField} />
</Li>
);
case ItemTypes.EDIT_FIELD:
return <FieldItem name={item.name} size={12} isEditing />;
default:
return null;
}

View File

@ -44,6 +44,7 @@ const NameWrapper = styled.div`
display: flex;
padding-left: 10px;
justify-content: space-between;
${({ isHidden }) => {
if (!isHidden) {
return css`
@ -65,6 +66,15 @@ const NameWrapper = styled.div`
`;
}
}}
${({ isEditing }) => {
if (isEditing) {
return css`
background: #e6f0fb;
border: 1px solid #aed4fb;
`;
}
}}
`;
const Wrapper = styled.div`

View File

@ -1,56 +1,81 @@
import React, { memo } from 'react';
import React, { forwardRef } from 'react';
import PropTypes from 'prop-types';
// import EditIcon from '../VariableEditIcon';
import GrabIconBlue from '../../assets/images/icon_grab_blue.svg';
import GrabIcon from '../../assets/images/icon_grab.svg';
import RemoveIcon from '../DraggedRemovedIcon';
import { Carret, FullWidthCarret, NameWrapper, Wrapper } from './components';
const FieldItem = ({
isDragging,
name,
showLeftCarret,
showRightCarret,
size,
type,
}) => {
const isHidden = name === '_TEMP_';
const withLongerHeight = [
'json',
'text',
'file',
'group',
'WYSIWYG',
].includes(type);
const style = withLongerHeight ? { height: '84px' } : {};
const FieldItem = forwardRef(
(
{
isDragging,
isEditing,
name,
showLeftCarret,
showRightCarret,
size,
type,
},
ref
) => {
const isHidden = name === '_TEMP_';
const withLongerHeight = [
'json',
'text',
'file',
'group',
'WYSIWYG',
].includes(type);
const style = withLongerHeight ? { height: '84px' } : {};
return (
<div style={{ width: `${(1 / 12) * size * 100}%` }}>
<Wrapper>
{isDragging && size === 12 ? (
<FullWidthCarret>
<div />
</FullWidthCarret>
) : (
<>
{showLeftCarret && <Carret style={style} />}
<div className="sub_wrapper">
<NameWrapper style={style} isHidden={isHidden}>
<span>{!isHidden && name}</span>
{!isHidden && (
<RemoveIcon withLongerHeight={withLongerHeight} />
)}
</NameWrapper>
</div>
{showRightCarret && <Carret right style={style} />}
</>
)}
</Wrapper>
</div>
);
};
return (
<div style={{ width: `${(1 / 12) * size * 100}%` }} ref={ref}>
<Wrapper>
{isDragging && size === 12 ? (
<FullWidthCarret>
<div />
</FullWidthCarret>
) : (
<>
{showLeftCarret && <Carret style={style} />}
<div className="sub_wrapper">
<NameWrapper
style={style}
isEditing={isEditing}
isHidden={isHidden}
>
<div>
{!isHidden && (
<img
src={isEditing ? GrabIconBlue : GrabIcon}
alt="Grab Icon"
style={{ marginRight: 10 }}
/>
)}
<span>{!isHidden && name}</span>
</div>
{!isHidden && (
<RemoveIcon
withLongerHeight={withLongerHeight}
isDragging={isEditing}
/>
)}
</NameWrapper>
</div>
{showRightCarret && <Carret right style={style} />}
</>
)}
</Wrapper>
</div>
);
}
);
FieldItem.defaultProps = {
isDragging: false,
isEditing: false,
showLeftCarret: false,
showRightCarret: false,
type: 'string',
@ -58,6 +83,7 @@ FieldItem.defaultProps = {
FieldItem.propTypes = {
isDragging: PropTypes.bool,
isEditing: PropTypes.bool,
name: PropTypes.string.isRequired,
showLeftCarret: PropTypes.bool,
showRightCarret: PropTypes.bool,
@ -65,4 +91,4 @@ FieldItem.propTypes = {
type: PropTypes.string,
};
export default memo(FieldItem);
export default FieldItem;

View File

@ -1,10 +1,152 @@
import React, { memo } from 'react';
import React, { useEffect, useRef } from 'react';
import { useDrag, useDrop } from 'react-dnd';
import { getEmptyImage } from 'react-dnd-html5-backend';
import PropTypes from 'prop-types';
import FieldItem from '../FieldItem';
const Item = ({ name, size, type }) => {
return <FieldItem name={name} size={size} type={type} />;
import ItemTypes from '../../utils/itemsTypes';
const Item = ({ itemIndex, moveRow, name, rowIndex, size, type }) => {
// console.log({ rowIndex });
const ref = useRef(null);
const [{ clientOffset, isOver }, drop] = useDrop({
accept: ItemTypes.EDIT_FIELD,
hover(item, monitor) {
// We use the hover only to reorder full size items
if (!ref.current) {
return;
}
if (item.size !== 12) {
return;
}
const dragIndex = monitor.getItem().itemIndex;
const hoverIndex = itemIndex;
const dragRow = monitor.getItem().rowIndex;
const targetRow = rowIndex;
// Don't replace item with themselves
if (dragIndex === hoverIndex && dragRow === targetRow) {
return;
}
// Determine rectangle on screen
const hoverBoundingRect = ref.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 (dragRow < targetRow && hoverClientY < hoverMiddleY) {
return;
}
// Dragging upwards
if (dragRow > targetRow && hoverClientY > hoverMiddleY) {
return;
}
// console.log({ item });
moveRow(dragRow, targetRow);
// 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.rowIndex = targetRow;
item.itemIndex = hoverIndex;
return;
},
drop(item, monitor) {
if (!ref.current) {
return;
}
console.log(monitor);
// const dragIndex = monitor.getItem().itemIndex;
// const hoverIndex = itemIndex;
// const dragRow = monitor.getItem().rowIndex;
// const targetRow = rowIndex;
if (item.size === 12) {
return;
}
return;
},
collect: monitor => ({
canDrop: monitor.canDrop(),
clientOffset: monitor.getClientOffset(),
isOver: monitor.isOver(),
isOverCurrent: monitor.isOver({ shallow: true }),
itemType: monitor.getItemType(),
}),
});
const [{ isDragging, getItem }, drag, preview] = useDrag({
canDrag() {
return name !== '_TEMP_';
},
collect: monitor => ({
isDragging: monitor.isDragging(),
getItem: monitor.getItem(),
}),
item: { type: ItemTypes.EDIT_FIELD, itemIndex, rowIndex, name, size },
});
useEffect(() => {
preview(getEmptyImage(), { captureDraggingState: true });
}, [preview]);
// Create the ref
drag(drop(ref));
let showLeftCarret = false;
let showRightCarret = false;
if (ref.current && clientOffset) {
const hoverBoundingRect = ref.current.getBoundingClientRect();
showLeftCarret =
isOver &&
getItem.size !== 12 &&
Math.abs(clientOffset.x - hoverBoundingRect.left) <
hoverBoundingRect.width / 2;
showRightCarret =
isOver &&
getItem.size !== 12 &&
// lastIndexOnLine === itemIndex &&
Math.abs(clientOffset.x - hoverBoundingRect.left) >
hoverBoundingRect.width / 2;
if (name === '_TEMP_') {
showLeftCarret = isOver;
showRightCarret = false;
}
}
return (
<FieldItem
isDragging={isDragging}
name={name}
showLeftCarret={showLeftCarret}
showRightCarret={showRightCarret}
size={size}
type={type}
ref={ref}
/>
);
};
Item.defaultProps = {
@ -12,9 +154,12 @@ Item.defaultProps = {
};
Item.propTypes = {
itemIndex: PropTypes.number.isRequired,
moveRow: PropTypes.func.isRequired,
name: PropTypes.string.isRequired,
rowIndex: PropTypes.number.isRequired,
size: PropTypes.number.isRequired,
type: PropTypes.string,
};
export default memo(Item);
export default Item;

View File

@ -5,7 +5,7 @@ import { get } from 'lodash';
import SortWrapper from '../SortWrapper';
import Item from './Item';
const FieldsReorder = ({ attributes, layout }) => {
const FieldsReorder = ({ attributes, layout, moveRow }) => {
const getType = attributeName => {
const attribute = get(attributes, [attributeName], {});
@ -30,6 +30,7 @@ const FieldsReorder = ({ attributes, layout }) => {
<Item
itemIndex={index}
key={name}
moveRow={moveRow}
name={name}
rowIndex={rowIndex}
size={size}
@ -54,6 +55,7 @@ FieldsReorder.defaultProps = {
FieldsReorder.propTypes = {
attributes: PropTypes.object,
layout: PropTypes.array,
moveRow: PropTypes.func.isRequired,
};
export default memo(FieldsReorder);

View File

@ -1,9 +1,11 @@
import { set } from 'lodash';
import {
ADD_FIELD_TO_LIST,
FORMAT_LAYOUT,
GET_DATA,
GET_DATA_SUCCEEDED,
MOVE_FIELD_LIST,
MOVE_ROW,
ON_CHANGE,
ON_REMOVE_LIST_FIELD,
ON_RESET,
@ -12,7 +14,7 @@ import {
SET_LIST_FIELD_TO_EDIT_INDEX,
SUBMIT_SUCCEEDED,
} from './constants';
import { formatLayout, createLayout } from '../../utils/layout';
import { formatLayout as updateLayout, createLayout } from '../../utils/layout';
export function addFieldToList(field) {
return {
@ -21,6 +23,12 @@ export function addFieldToList(field) {
};
}
export function formatLayout() {
return {
type: FORMAT_LAYOUT,
};
}
export function getData(uid) {
return {
type: GET_DATA,
@ -32,7 +40,7 @@ export function getDataSucceeded(layout) {
set(
layout,
['layouts', 'edit'],
formatLayout(createLayout(layout.layouts.edit))
updateLayout(createLayout(layout.layouts.edit))
);
return {
type: GET_DATA_SUCCEEDED,
@ -48,6 +56,14 @@ export function moveListField(dragIndex, overIndex) {
};
}
export function moveRow(dragRowIndex, hoverRowIndex) {
return {
type: MOVE_ROW,
dragRowIndex,
hoverRowIndex,
};
}
export function onChange({ target: { name, value } }) {
return {
type: ON_CHANGE,

View File

@ -1,10 +1,12 @@
export const ADD_FIELD_TO_LIST =
'ContentManager/SettingViewModel/ADD_FIELD_TO_LIST';
export const GET_DATA = 'ContentManager/SettingViewModel/GET_DATA';
export const FORMAT_LAYOUT = 'ContentManager/SettingViewModel/FORMAT_LAYOUT';
export const GET_DATA_SUCCEEDED =
'ContentManager/SettingViewModel/GET_DATA_SUCCEEDED';
export const MOVE_FIELD_LIST =
'ContentManager/SettingViewModel/MOVE_FIELD_LIST';
export const MOVE_ROW = 'ContentManager/SettingViewModel/MOVE_ROW';
export const ON_CHANGE = 'ContentManager/SettingViewModel/ON_CHANGE';
export const ON_REMOVE_LIST_FIELD =
'ContentManager/SettingViewModel/ON_REMOVE_LIST_FIELD';

View File

@ -26,8 +26,10 @@ import Separator from './Separator';
import {
addFieldToList,
formatLayout,
getData,
moveListField,
moveRow,
onChange,
onReset,
onSubmit,
@ -46,7 +48,9 @@ const getUrl = (name, to) =>
function SettingViewModel({
addFieldToList,
didDrop,
emitEvent,
formatLayout,
getData,
history: { goBack },
initialData,
@ -57,6 +61,7 @@ function SettingViewModel({
},
modifiedData,
moveListField,
moveRow,
onChange,
onRemoveListField,
onReset,
@ -87,6 +92,13 @@ function SettingViewModel({
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [shouldToggleModalSubmit]);
useEffect(() => {
if (!isLoading) {
formatLayout();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [didDrop]);
if (isLoading) {
return <LoadingIndicatorPage />;
}
@ -241,6 +253,7 @@ function SettingViewModel({
<FieldsReorder
attributes={get(modifiedData, ['schema', 'attributes'], {})}
layout={get(modifiedData, ['layouts', 'edit'], [])}
moveRow={moveRow}
/>
)}
</div>
@ -281,7 +294,9 @@ function SettingViewModel({
SettingViewModel.propTypes = {
addFieldToList: PropTypes.func.isRequired,
didDrop: PropTypes.bool.isRequired,
emitEvent: PropTypes.func.isRequired,
formatLayout: PropTypes.func.isRequired,
getData: PropTypes.func.isRequired,
history: PropTypes.shape({
goBack: PropTypes.func,
@ -295,9 +310,9 @@ SettingViewModel.propTypes = {
settingType: PropTypes.string,
}),
}).isRequired,
modifiedData: PropTypes.object.isRequired,
moveListField: PropTypes.func.isRequired,
moveRow: PropTypes.func.isRequired,
onChange: PropTypes.func.isRequired,
onRemoveListField: PropTypes.func.isRequired,
onReset: PropTypes.func.isRequired,
@ -313,8 +328,10 @@ export function mapDispatchToProps(dispatch) {
return bindActionCreators(
{
addFieldToList,
formatLayout,
getData,
moveListField,
moveRow,
onChange,
onRemoveListField,
onReset,

View File

@ -4,11 +4,14 @@
*/
import { fromJS } from 'immutable';
import { formatLayout } from '../../utils/layout';
import {
ADD_FIELD_TO_LIST,
FORMAT_LAYOUT,
GET_DATA_SUCCEEDED,
MOVE_FIELD_LIST,
MOVE_ROW,
ON_CHANGE,
ON_REMOVE_LIST_FIELD,
ON_RESET,
@ -18,6 +21,7 @@ import {
} from './constants';
export const initialState = fromJS({
didDrop: false,
listFieldToEditIndex: 0,
initialData: fromJS({}),
isLoading: true,
@ -26,11 +30,20 @@ export const initialState = fromJS({
});
function settingViewModelReducer(state = initialState, action) {
const layoutPath = ['modifiedData', 'layouts', 'edit'];
const { dragIndex, hoverIndex, dragRowIndex, hoverRowIndex } = action;
console.log(dragIndex, hoverIndex);
switch (action.type) {
case ADD_FIELD_TO_LIST:
return state.updateIn(['modifiedData', 'layouts', 'list'], list =>
list.push(action.field)
);
case FORMAT_LAYOUT: {
const newList = formatLayout(state.getIn(layoutPath).toJS());
return state.updateIn(layoutPath, () => fromJS(newList));
}
case GET_DATA_SUCCEEDED:
return state
.update('initialData', () => fromJS(action.layout || {}))
@ -46,6 +59,13 @@ function settingViewModelReducer(state = initialState, action) {
.update('listFieldToEditIndex', () => {
return action.overIndex;
});
case MOVE_ROW:
return state.updateIn(layoutPath, list => {
return list
.delete(dragRowIndex)
.insert(hoverRowIndex, state.getIn([...layoutPath, dragRowIndex]));
});
case ON_CHANGE:
return state.updateIn(action.keys, () => action.value);
case ON_REMOVE_LIST_FIELD: {

View File

@ -2,4 +2,5 @@ export default {
FIELD: 'field',
GROUP: 'group',
RELATION: 'relation',
EDIT_FIELD: 'editField',
};