From c088a96560ed81af8c3e8da1b2b4098a0fb648d0 Mon Sep 17 00:00:00 2001 From: soupette Date: Mon, 8 Jul 2019 15:40:01 +0200 Subject: [PATCH] Add dnd sort for list --- .../src/containers/SettingPage/constants.js | 9 +- .../containers/SettingViewModel/ListField.js | 96 ++++++-- .../containers/SettingViewModel/ListLayout.js | 72 ++++-- .../containers/SettingViewModel/actions.js | 9 + .../containers/SettingViewModel/components.js | 7 +- .../containers/SettingViewModel/constants.js | 2 + .../src/containers/SettingViewModel/index.js | 217 +++++++++--------- .../containers/SettingViewModel/itemsTypes.js | 3 + .../containers/SettingViewModel/reducer.js | 7 + 9 files changed, 287 insertions(+), 135 deletions(-) create mode 100644 packages/strapi-plugin-content-manager/admin/src/containers/SettingViewModel/itemsTypes.js diff --git a/packages/strapi-plugin-content-manager/admin/src/containers/SettingPage/constants.js b/packages/strapi-plugin-content-manager/admin/src/containers/SettingPage/constants.js index 40ae4cd939..8f4a546292 100644 --- a/packages/strapi-plugin-content-manager/admin/src/containers/SettingPage/constants.js +++ b/packages/strapi-plugin-content-manager/admin/src/containers/SettingPage/constants.js @@ -1,3 +1,6 @@ -export const ON_CLICK_EDIT_FIELD = 'contentManager/SettingPage/ON_CLICK_EDIT_FIELD'; -export const ON_CLICK_EDIT_LIST_ITEM = 'contentManager/SettingPage/ON_CLICK_EDIT_LIST_ITEM'; -export const ON_CLICK_EDIT_RELATION = 'contentManager/SettingPage/ON_CLICK_EDIT_RELATION'; \ No newline at end of file +export const ON_CLICK_EDIT_FIELD = + 'contentManager/SettingPage/ON_CLICK_EDIT_FIELD'; +export const ON_CLICK_EDIT_LIST_ITEM = + 'contentManager/SettingPage/ON_CLICK_EDIT_LIST_ITEM'; +export const ON_CLICK_EDIT_RELATION = + 'contentManager/SettingPage/ON_CLICK_EDIT_RELATION'; diff --git a/packages/strapi-plugin-content-manager/admin/src/containers/SettingViewModel/ListField.js b/packages/strapi-plugin-content-manager/admin/src/containers/SettingViewModel/ListField.js index 1ca16b0331..2ca5970b94 100644 --- a/packages/strapi-plugin-content-manager/admin/src/containers/SettingViewModel/ListField.js +++ b/packages/strapi-plugin-content-manager/admin/src/containers/SettingViewModel/ListField.js @@ -1,6 +1,7 @@ -import React, { memo, useState } from 'react'; +import React, { useState, useRef } from 'react'; import PropTypes from 'prop-types'; -import { Field, Wrapper, InfoLabel } from './components'; +import { Field, InfoLabel } from './components'; +import { DragSource, DropTarget } from 'react-dnd'; import GrabIcon from '../../assets/images/icon_grab.svg'; import GrabIconBlue from '../../assets/images/icon_grab_blue.svg'; @@ -8,21 +9,41 @@ import ClickOverHint from '../../components/ClickOverHint'; import RemoveIcon from '../../components/DraggedRemovedIcon'; import EditIcon from '../../components/VariableEditIcon'; -function ListField({ index, isSelected, label, name, onClick, onRemove }) { +import ItemTypes from './itemsTypes'; + +function ListField({ + index, + isDragging, + isSelected, + label, + name, + onClick, + onRemove, + connectDragSource, + connectDropTarget, +}) { + const opacity = isDragging ? 0.2 : 1; + + const ref = useRef(null); const [isOver, setIsOver] = useState(false); const showLabel = (!isOver || isSelected) && label.toLowerCase() !== name.toLowerCase(); + connectDragSource(ref); + connectDropTarget(ref); + return ( - setIsOver(true)} - onMouseLeave={() => setIsOver(false)} - onClick={() => { - onClick(index); - }} - > -
{index + 1}.
- + <> + setIsOver(true)} + onMouseLeave={() => setIsOver(false)} + onClick={() => { + onClick(index); + }} + ref={ref} + isSelected={isSelected} + style={{ opacity }} + > {name} @@ -39,23 +60,70 @@ function ListField({ index, isSelected, label, name, onClick, onRemove }) { /> )} -
+ ); } ListField.defaultProps = { label: '', + move: () => {}, onClick: () => {}, onRemove: () => {}, }; ListField.propTypes = { + connectDragSource: PropTypes.func.isRequired, + connectDropTarget: PropTypes.func.isRequired, index: PropTypes.number.isRequired, + isDragging: PropTypes.bool.isRequired, isSelected: PropTypes.bool.isRequired, label: PropTypes.string, + move: PropTypes.func, name: PropTypes.string.isRequired, onClick: PropTypes.func, onRemove: PropTypes.func, }; -export default memo(ListField); +export default DropTarget( + ItemTypes.FIELD, + { + canDrop: () => false, + hover(props, monitor) { + const { id: draggedId } = monitor.getItem(); + const { name: overId } = props; + + if (draggedId !== overId) { + const { index: overIndex } = props.findField(overId); + props.move(draggedId, overIndex); + } + }, + }, + connect => ({ + connectDropTarget: connect.dropTarget(), + }) +)( + DragSource( + ItemTypes.FIELD, + { + beginDrag: props => { + return { + id: props.name, + originalIndex: props.findField(props.name).index, + }; + }, + + endDrag(props, monitor) { + const { id: droppedId, originalIndex } = monitor.getItem(); + const didDrop = monitor.didDrop(); + + if (!didDrop) { + props.move(droppedId, originalIndex); + } + }, + }, + (connect, monitor) => ({ + connectDragSource: connect.dragSource(), + isDragging: monitor.isDragging(), + }) + )(ListField) +); diff --git a/packages/strapi-plugin-content-manager/admin/src/containers/SettingViewModel/ListLayout.js b/packages/strapi-plugin-content-manager/admin/src/containers/SettingViewModel/ListLayout.js index 575910fb31..7cfa9cc697 100644 --- a/packages/strapi-plugin-content-manager/admin/src/containers/SettingViewModel/ListLayout.js +++ b/packages/strapi-plugin-content-manager/admin/src/containers/SettingViewModel/ListLayout.js @@ -1,26 +1,32 @@ -import React from 'react'; +import React, { Fragment, useCallback, useRef } from 'react'; import PropTypes from 'prop-types'; import { get } from 'lodash'; +import { DropTarget } from 'react-dnd'; import { InputsIndex as Input } from 'strapi-helper-plugin'; import pluginId from '../../pluginId'; import FormWrapper from '../../components/SettingFormWrapper'; - +import { Wrapper } from './components'; import Add from './Add'; import ListField from './ListField'; +import ItemTypes from './itemsTypes'; + function ListLayout({ addField, availableData, + connectDropTarget, displayedData, fieldToEditIndex, modifiedData, + moveListField, onChange, onClick, onRemove, }) { + const ref = useRef(null); const handleRemove = index => { if (displayedData.length > 1) { onRemove(index); @@ -29,6 +35,7 @@ function ListLayout({ strapi.notification.info(`${pluginId}.notification.info.minimumFields`); }; + const fieldName = displayedData[fieldToEditIndex]; const fieldPath = ['metadata', fieldName, 'list']; @@ -56,19 +63,54 @@ function ListLayout({ validations: {}, }, ]; + + const findField = useCallback( + id => { + const field = displayedData.filter(current => current === id)[0]; + + return { + field, + index: displayedData.indexOf(field), + }; + }, + [displayedData] + ); + + const move = useCallback( + (id, atIndex) => { + const { index } = findField(id); + + moveListField(index, atIndex); + }, + [displayedData] + ); + + connectDropTarget(ref); + return ( <> -
+
{displayedData.map((data, index) => ( - + + +
{index + 1}.
+ +
+
+
))}
@@ -104,12 +146,16 @@ ListLayout.defaultProps = { ListLayout.propTypes = { addField: PropTypes.func, availableData: PropTypes.array, + connectDropTarget: PropTypes.func.isRequired, displayedData: PropTypes.array, fieldToEditIndex: PropTypes.number, modifiedData: PropTypes.object, + moveListField: PropTypes.func.isRequired, onChange: PropTypes.func, onClick: PropTypes.func, onRemove: PropTypes.func, }; -export default ListLayout; +export default DropTarget(ItemTypes.FIELD, {}, connect => ({ + connectDropTarget: connect.dropTarget(), +}))(ListLayout); diff --git a/packages/strapi-plugin-content-manager/admin/src/containers/SettingViewModel/actions.js b/packages/strapi-plugin-content-manager/admin/src/containers/SettingViewModel/actions.js index e6326a60d5..54c6f91da1 100644 --- a/packages/strapi-plugin-content-manager/admin/src/containers/SettingViewModel/actions.js +++ b/packages/strapi-plugin-content-manager/admin/src/containers/SettingViewModel/actions.js @@ -2,6 +2,7 @@ import { ADD_FIELD_TO_LIST, GET_DATA, GET_DATA_SUCCEEDED, + MOVE_FIELD_LIST, ON_CHANGE, ON_REMOVE_LIST_FIELD, ON_RESET, @@ -32,6 +33,14 @@ export function getDataSucceeded(layout) { }; } +export function moveListField(dragIndex, overIndex) { + return { + type: MOVE_FIELD_LIST, + dragIndex, + overIndex, + }; +} + export function onChange({ target: { name, value } }) { return { type: ON_CHANGE, diff --git a/packages/strapi-plugin-content-manager/admin/src/containers/SettingViewModel/components.js b/packages/strapi-plugin-content-manager/admin/src/containers/SettingViewModel/components.js index 76f7ad45c5..530667998a 100644 --- a/packages/strapi-plugin-content-manager/admin/src/containers/SettingViewModel/components.js +++ b/packages/strapi-plugin-content-manager/admin/src/containers/SettingViewModel/components.js @@ -2,6 +2,7 @@ import styled, { css } from 'styled-components'; const Wrapper = styled.div` display: flex; + width: 100%; > div:first-child { height: 30px; width: 20px; @@ -9,13 +10,15 @@ const Wrapper = styled.div` text-align: right; line-height: 30px; } + > div:last-child { + flex-grow: 2; + } `; const Field = styled.div` position: relative; height: 30px; width: 100%; - margin-bottom: 6px; padding-left: 10px; justify-content: space-between; background: #fafafb; @@ -49,7 +52,7 @@ const InfoLabel = styled.div` position: absolute; top: 0; right: 40px; - color: #858b9a; + // color: #858b9a; font-weight: 400; color: #007eff; `; diff --git a/packages/strapi-plugin-content-manager/admin/src/containers/SettingViewModel/constants.js b/packages/strapi-plugin-content-manager/admin/src/containers/SettingViewModel/constants.js index 03ffc01d9d..58da4cc889 100644 --- a/packages/strapi-plugin-content-manager/admin/src/containers/SettingViewModel/constants.js +++ b/packages/strapi-plugin-content-manager/admin/src/containers/SettingViewModel/constants.js @@ -3,6 +3,8 @@ export const ADD_FIELD_TO_LIST = export const GET_DATA = 'ContentManager/SettingViewModel/GET_DATA'; export const GET_DATA_SUCCEEDED = 'ContentManager/SettingViewModel/GET_DATA_SUCCEEDED'; +export const MOVE_FIELD_LIST = + 'ContentManager/SettingViewModel/MOVE_FIELD_LIST'; export const ON_CHANGE = 'ContentManager/SettingViewModel/ON_CHANGE'; export const ON_REMOVE_LIST_FIELD = 'ContentManager/SettingViewModel/ON_REMOVE_LIST_FIELD'; diff --git a/packages/strapi-plugin-content-manager/admin/src/containers/SettingViewModel/index.js b/packages/strapi-plugin-content-manager/admin/src/containers/SettingViewModel/index.js index 4175e56675..15d8398c81 100644 --- a/packages/strapi-plugin-content-manager/admin/src/containers/SettingViewModel/index.js +++ b/packages/strapi-plugin-content-manager/admin/src/containers/SettingViewModel/index.js @@ -3,6 +3,8 @@ import PropTypes from 'prop-types'; import { connect } from 'react-redux'; import { bindActionCreators, compose } from 'redux'; import { get, isEqual, isEmpty, upperFirst } from 'lodash'; +import { DndProvider } from 'react-dnd'; +import HTML5Backend from 'react-dnd-html5-backend'; import { BackHeader, @@ -26,6 +28,7 @@ import Separator from './Separator'; import { addFieldToList, getData, + moveListField, onChange, onReset, onSubmit, @@ -54,6 +57,7 @@ function SettingViewModel({ params: { name, settingType }, }, modifiedData, + moveListField, onChange, onRemoveListField, onReset, @@ -131,113 +135,118 @@ function SettingViewModel({ return ( <> - goBack()} /> - - - -
- + goBack()} /> + + - -
- {forms[settingType].map(input => { - return ( - - ); - })} -
- + description={{ + id: + 'content-manager.containers.SettingPage.pluginHeaderDescription', + }} + /> + +
+ + +
+ {forms[settingType].map(input => { + return ( + + ); + })} +
+ +
-
- + -
- - - +
+ + + - {settingType === 'list-settings' && ( - - )} -
- -
- - { - onReset(); - toggleWarningCancel(); - }} - /> - onSubmit(name, emitEvent)} - /> + {settingType === 'list-settings' && ( + + )} +
+ +
+
+ { + onReset(); + toggleWarningCancel(); + }} + /> + onSubmit(name, emitEvent)} + /> + ); } @@ -260,6 +269,7 @@ SettingViewModel.propTypes = { }).isRequired, modifiedData: PropTypes.object.isRequired, + moveListField: PropTypes.func.isRequired, onChange: PropTypes.func.isRequired, onRemoveListField: PropTypes.func.isRequired, onReset: PropTypes.func.isRequired, @@ -276,6 +286,7 @@ export function mapDispatchToProps(dispatch) { { addFieldToList, getData, + moveListField, onChange, onRemoveListField, onReset, diff --git a/packages/strapi-plugin-content-manager/admin/src/containers/SettingViewModel/itemsTypes.js b/packages/strapi-plugin-content-manager/admin/src/containers/SettingViewModel/itemsTypes.js new file mode 100644 index 0000000000..b052160ab4 --- /dev/null +++ b/packages/strapi-plugin-content-manager/admin/src/containers/SettingViewModel/itemsTypes.js @@ -0,0 +1,3 @@ +export default { + FIELD: 'field', +}; diff --git a/packages/strapi-plugin-content-manager/admin/src/containers/SettingViewModel/reducer.js b/packages/strapi-plugin-content-manager/admin/src/containers/SettingViewModel/reducer.js index 3ea1a95fb0..648a8b4c58 100644 --- a/packages/strapi-plugin-content-manager/admin/src/containers/SettingViewModel/reducer.js +++ b/packages/strapi-plugin-content-manager/admin/src/containers/SettingViewModel/reducer.js @@ -7,6 +7,7 @@ import { fromJS } from 'immutable'; import { ADD_FIELD_TO_LIST, GET_DATA_SUCCEEDED, + MOVE_FIELD_LIST, ON_CHANGE, ON_REMOVE_LIST_FIELD, ON_RESET, @@ -34,6 +35,12 @@ function settingViewModelReducer(state = initialState, action) { .update('initialData', () => fromJS(action.layout)) .update('isLoading', () => false) .update('modifiedData', () => fromJS(action.layout)); + case MOVE_FIELD_LIST: + return state.updateIn(['modifiedData', 'layouts', 'list'], list => { + return list + .delete(action.dragIndex) + .insert(action.overIndex, list.get(action.dragIndex)); + }); case ON_CHANGE: return state.updateIn(action.keys, () => action.value); case ON_REMOVE_LIST_FIELD: {