mirror of
https://github.com/strapi/strapi.git
synced 2025-12-27 07:03:38 +00:00
Handle reorder full size elements
This commit is contained in:
parent
fab10fea20
commit
f12b8f8c87
@ -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;
|
||||
}
|
||||
|
||||
@ -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`
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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';
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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: {
|
||||
|
||||
@ -2,4 +2,5 @@ export default {
|
||||
FIELD: 'field',
|
||||
GROUP: 'group',
|
||||
RELATION: 'relation',
|
||||
EDIT_FIELD: 'editField',
|
||||
};
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user