From 3e808a1983acc7e8703876230a7d0b2666fb9a8b Mon Sep 17 00:00:00 2001 From: Gustav Hansen Date: Fri, 18 Feb 2022 13:55:10 +0100 Subject: [PATCH 1/4] ListSettingsView: Append the new field instead of prepend --- .../admin/src/content-manager/pages/ListSettingsView/reducer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/admin/admin/src/content-manager/pages/ListSettingsView/reducer.js b/packages/core/admin/admin/src/content-manager/pages/ListSettingsView/reducer.js index a232257da8..4fd33f238d 100644 --- a/packages/core/admin/admin/src/content-manager/pages/ListSettingsView/reducer.js +++ b/packages/core/admin/admin/src/content-manager/pages/ListSettingsView/reducer.js @@ -17,7 +17,7 @@ const reducer = (state = initialState, action) => switch (action.type) { case 'ADD_FIELD': { const layoutFieldList = get(state, layoutFieldListPath, []); - set(draftState, layoutFieldListPath, [action.item, ...layoutFieldList]); + set(draftState, layoutFieldListPath, [...layoutFieldList, action.item]); break; } case 'MOVE_FIELD': { From fa5ab956f2c30392413e31e253516e49567b4908 Mon Sep 17 00:00:00 2001 From: Gustav Hansen Date: Fri, 18 Feb 2022 18:57:36 +0100 Subject: [PATCH 2/4] ListSettingsView: Scroll to end of listview after a field was added --- .../components/DraggableCard.js | 341 +++++++++--------- .../components/SortDisplayedFields.js | 21 +- 2 files changed, 194 insertions(+), 168 deletions(-) diff --git a/packages/core/admin/admin/src/content-manager/pages/ListSettingsView/components/DraggableCard.js b/packages/core/admin/admin/src/content-manager/pages/ListSettingsView/components/DraggableCard.js index f4fa5bd010..f95d96b1d6 100644 --- a/packages/core/admin/admin/src/content-manager/pages/ListSettingsView/components/DraggableCard.js +++ b/packages/core/admin/admin/src/content-manager/pages/ListSettingsView/components/DraggableCard.js @@ -1,4 +1,4 @@ -import React, { useEffect, useRef, useState } from 'react'; +import React, { useEffect, useRef, useState, forwardRef } from 'react'; import styled from 'styled-components'; import PropTypes from 'prop-types'; import { useDrag, useDrop } from 'react-dnd'; @@ -75,180 +75,189 @@ const FieldWrapper = styled(Box)` } `; -const DraggableCard = ({ - index, - isDraggingSibling, - labelField, - onClickEditField, - onMoveField, - onRemoveField, - name, - setIsDraggingSibling, -}) => { - const { formatMessage } = useIntl(); - const dragRef = useRef(null); - const dropRef = useRef(null); - const [, forceRerenderAfterDnd] = useState(false); - const editButtonRef = useRef(); - const cardEllipsisTitle = ellipsisCardTitle(labelField); - - const handleClickEditRow = () => { - if (editButtonRef.current) { - editButtonRef.current.click(); - } - }; - - const [, drop] = useDrop({ - accept: ItemTypes.FIELD, - hover(item, monitor) { - if (!dropRef.current) { - return; - } - const dragIndex = item.index; - const hoverIndex = index; - - // Don't replace items with themselves - if (dragIndex === hoverIndex) { - return; - } - - // Determine rectangle on screen - const hoverBoundingRect = dropRef.current.getBoundingClientRect(); - // Get vertical middle - const hoverMiddleX = (hoverBoundingRect.right - hoverBoundingRect.left) / 2; - // Determine mouse position - const clientOffset = monitor.getClientOffset(); - // Get pixels to the top - const hoverClientX = clientOffset.x - hoverBoundingRect.left; - - // 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 && hoverClientX > hoverMiddleX) { - return; - } - // Dragging upwards - if (dragIndex < hoverIndex && hoverClientX < hoverMiddleX) { - return; - } - - onMoveField(dragIndex, hoverIndex); - - item.index = hoverIndex; +const DraggableCard = forwardRef( + ( + { + index, + isDraggingSibling, + labelField, + onClickEditField, + onMoveField, + onRemoveField, + name, + setIsDraggingSibling, }, - }); + ref + ) => { + const { formatMessage } = useIntl(); + const dragRef = useRef(null); + const dropRef = useRef(null); + const [, forceRerenderAfterDnd] = useState(false); + const editButtonRef = useRef(); + const cardEllipsisTitle = ellipsisCardTitle(labelField); - const [{ isDragging }, drag, preview] = useDrag({ - type: ItemTypes.FIELD, - item: () => { - return { index, labelField, name }; - }, - collect: monitor => ({ - isDragging: monitor.isDragging(), - }), - end: () => { - setIsDraggingSibling(false); - }, - }); + const handleClickEditRow = () => { + if (editButtonRef.current) { + editButtonRef.current.click(); + } + }; - useEffect(() => { - preview(getEmptyImage(), { captureDraggingState: false }); - }, [preview]); + const [, drop] = useDrop({ + accept: ItemTypes.FIELD, + hover(item, monitor) { + if (!dropRef.current) { + return; + } + const dragIndex = item.index; + const hoverIndex = index; - useEffect(() => { - if (isDragging) { - setIsDraggingSibling(true); - } - }, [isDragging, setIsDraggingSibling]); + // Don't replace items with themselves + if (dragIndex === hoverIndex) { + return; + } - // Effect in order to force a rerender after reordering the components - // Since we are removing the Accordion when doing the DnD we are losing the dragRef, therefore the replaced element cannot be dragged - // anymore, this hack forces a rerender in order to apply the dragRef - useEffect(() => { - if (!isDraggingSibling) { - forceRerenderAfterDnd(prev => !prev); - } - }, [isDraggingSibling]); + // Determine rectangle on screen + const hoverBoundingRect = dropRef.current.getBoundingClientRect(); + // Get vertical middle + const hoverMiddleX = (hoverBoundingRect.right - hoverBoundingRect.left) / 2; + // Determine mouse position + const clientOffset = monitor.getClientOffset(); + // Get pixels to the top + const hoverClientX = clientOffset.x - hoverBoundingRect.left; - // Create the refs - // We need 1 for the drop target - // 1 for the drag target - const refs = { - dragRef: drag(dragRef), - dropRef: drop(dropRef), - }; + // 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 && hoverClientX > hoverMiddleX) { + return; + } + // Dragging upwards + if (dragIndex < hoverIndex && hoverClientX < hoverMiddleX) { + return; + } - return ( - - {isDragging && } - {!isDragging && isDraggingSibling && } + onMoveField(dragIndex, hoverIndex); - {!isDragging && !isDraggingSibling && ( - - - e.stopPropagation()} - ref={refs.dragRef} - type="button" + item.index = hoverIndex; + }, + }); + + const [{ isDragging }, drag, preview] = useDrag({ + type: ItemTypes.FIELD, + item: () => { + return { index, labelField, name }; + }, + collect: monitor => ({ + isDragging: monitor.isDragging(), + }), + end: () => { + setIsDraggingSibling(false); + }, + }); + + useEffect(() => { + preview(getEmptyImage(), { captureDraggingState: false }); + }, [preview]); + + useEffect(() => { + if (isDragging) { + setIsDraggingSibling(true); + } + }, [isDragging, setIsDraggingSibling]); + + // Effect in order to force a rerender after reordering the components + // Since we are removing the Accordion when doing the DnD we are losing the dragRef, therefore the replaced element cannot be dragged + // anymore, this hack forces a rerender in order to apply the dragRef + useEffect(() => { + if (!isDraggingSibling) { + forceRerenderAfterDnd(prev => !prev); + } + }, [isDraggingSibling]); + + // Create the refs + // We need 1 for the drop target + // 1 for the drag target + const refs = { + dragRef: drag(dragRef), + dropRef: drop(dropRef), + }; + + return ( +
+ + {isDragging && } + {!isDragging && isDraggingSibling && ( + + )} + + {!isDragging && !isDraggingSibling && ( + - - - {cardEllipsisTitle} - - - { - e.stopPropagation(); - onClickEditField(name); - }} - aria-label={formatMessage( - { - id: getTrad('components.DraggableCard.edit.field'), - defaultMessage: 'Edit {item}', - }, - { item: name } - )} - type="button" - > - - - - - - - - )} - - ); -}; + + e.stopPropagation()} + ref={refs.dragRef} + type="button" + > + + + {cardEllipsisTitle} + + + { + e.stopPropagation(); + onClickEditField(name); + }} + aria-label={formatMessage( + { + id: getTrad('components.DraggableCard.edit.field'), + defaultMessage: 'Edit {item}', + }, + { item: name } + )} + type="button" + > + + + + + + + + )} + +
+ ); + } +); DraggableCard.propTypes = { index: PropTypes.number.isRequired, diff --git a/packages/core/admin/admin/src/content-manager/pages/ListSettingsView/components/SortDisplayedFields.js b/packages/core/admin/admin/src/content-manager/pages/ListSettingsView/components/SortDisplayedFields.js index e68ba58aba..e9aefccb71 100644 --- a/packages/core/admin/admin/src/content-manager/pages/ListSettingsView/components/SortDisplayedFields.js +++ b/packages/core/admin/admin/src/content-manager/pages/ListSettingsView/components/SortDisplayedFields.js @@ -1,5 +1,6 @@ -import React, { useState } from 'react'; +import React, { useState, useRef, useEffect } from 'react'; import styled from 'styled-components'; +import last from 'lodash/last'; import { PropTypes } from 'prop-types'; import { useIntl } from 'react-intl'; import { Box } from '@strapi/design-system/Box'; @@ -36,6 +37,21 @@ const SortDisplayedFields = ({ }) => { const { formatMessage } = useIntl(); const [isDraggingSibling, setIsDraggingSibling] = useState(false); + const [fieldWasAdded, setFieldWasAdded] = useState(false); + const fieldRefMap = useRef([]); + + function handleAddField(field) { + onAddField(field); + setFieldWasAdded(true); + } + + useEffect(() => { + if (fieldWasAdded) { + const lastField = last(fieldRefMap.current); + + lastField.scrollIntoView(); + } + }, [displayedFields, fieldWasAdded]); return ( <> @@ -69,6 +85,7 @@ const SortDisplayedFields = ({ name={field} labelField={metadatas[field].list.label || field} setIsDraggingSibling={setIsDraggingSibling} + ref={el => fieldRefMap.current.push(el)} /> ))}
@@ -85,7 +102,7 @@ const SortDisplayedFields = ({ data-testid="add-field" > {listRemainingFields.map(field => ( - onAddField(field)}> + handleAddField(field)}> {field} ))} From 8207b5638030fe3acb8737a5f4c823262e5710e0 Mon Sep 17 00:00:00 2001 From: Gustav Hansen Date: Mon, 21 Feb 2022 16:21:17 +0100 Subject: [PATCH 3/4] ListSettingsView: Simplify scrollLeft effect --- .../components/DraggableCard.js | 341 +++++++++--------- .../components/SortDisplayedFields.js | 29 +- 2 files changed, 181 insertions(+), 189 deletions(-) diff --git a/packages/core/admin/admin/src/content-manager/pages/ListSettingsView/components/DraggableCard.js b/packages/core/admin/admin/src/content-manager/pages/ListSettingsView/components/DraggableCard.js index f95d96b1d6..4bb237f842 100644 --- a/packages/core/admin/admin/src/content-manager/pages/ListSettingsView/components/DraggableCard.js +++ b/packages/core/admin/admin/src/content-manager/pages/ListSettingsView/components/DraggableCard.js @@ -1,4 +1,4 @@ -import React, { useEffect, useRef, useState, forwardRef } from 'react'; +import React, { useEffect, useRef, useState } from 'react'; import styled from 'styled-components'; import PropTypes from 'prop-types'; import { useDrag, useDrop } from 'react-dnd'; @@ -75,189 +75,180 @@ const FieldWrapper = styled(Box)` } `; -const DraggableCard = forwardRef( - ( - { - index, - isDraggingSibling, - labelField, - onClickEditField, - onMoveField, - onRemoveField, - name, - setIsDraggingSibling, +const DraggableCard = ({ + index, + isDraggingSibling, + labelField, + onClickEditField, + onMoveField, + onRemoveField, + name, + setIsDraggingSibling, +}) => { + const { formatMessage } = useIntl(); + const dragRef = useRef(null); + const dropRef = useRef(null); + const [, forceRerenderAfterDnd] = useState(false); + const editButtonRef = useRef(); + const cardEllipsisTitle = ellipsisCardTitle(labelField); + + const handleClickEditRow = () => { + if (editButtonRef.current) { + editButtonRef.current.click(); + } + }; + + const [, drop] = useDrop({ + accept: ItemTypes.FIELD, + hover(item, monitor) { + if (!dropRef.current) { + return; + } + const dragIndex = item.index; + const hoverIndex = index; + + // Don't replace items with themselves + if (dragIndex === hoverIndex) { + return; + } + + // Determine rectangle on screen + const hoverBoundingRect = dropRef.current.getBoundingClientRect(); + // Get vertical middle + const hoverMiddleX = (hoverBoundingRect.right - hoverBoundingRect.left) / 2; + // Determine mouse position + const clientOffset = monitor.getClientOffset(); + // Get pixels to the top + const hoverClientX = clientOffset.x - hoverBoundingRect.left; + + // 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 && hoverClientX > hoverMiddleX) { + return; + } + // Dragging upwards + if (dragIndex < hoverIndex && hoverClientX < hoverMiddleX) { + return; + } + + onMoveField(dragIndex, hoverIndex); + + item.index = hoverIndex; }, - ref - ) => { - const { formatMessage } = useIntl(); - const dragRef = useRef(null); - const dropRef = useRef(null); - const [, forceRerenderAfterDnd] = useState(false); - const editButtonRef = useRef(); - const cardEllipsisTitle = ellipsisCardTitle(labelField); + }); - const handleClickEditRow = () => { - if (editButtonRef.current) { - editButtonRef.current.click(); - } - }; + const [{ isDragging }, drag, preview] = useDrag({ + type: ItemTypes.FIELD, + item: () => { + return { index, labelField, name }; + }, + collect: monitor => ({ + isDragging: monitor.isDragging(), + }), + end: () => { + setIsDraggingSibling(false); + }, + }); - const [, drop] = useDrop({ - accept: ItemTypes.FIELD, - hover(item, monitor) { - if (!dropRef.current) { - return; - } - const dragIndex = item.index; - const hoverIndex = index; + useEffect(() => { + preview(getEmptyImage(), { captureDraggingState: false }); + }, [preview]); - // Don't replace items with themselves - if (dragIndex === hoverIndex) { - return; - } + useEffect(() => { + if (isDragging) { + setIsDraggingSibling(true); + } + }, [isDragging, setIsDraggingSibling]); - // Determine rectangle on screen - const hoverBoundingRect = dropRef.current.getBoundingClientRect(); - // Get vertical middle - const hoverMiddleX = (hoverBoundingRect.right - hoverBoundingRect.left) / 2; - // Determine mouse position - const clientOffset = monitor.getClientOffset(); - // Get pixels to the top - const hoverClientX = clientOffset.x - hoverBoundingRect.left; + // Effect in order to force a rerender after reordering the components + // Since we are removing the Accordion when doing the DnD we are losing the dragRef, therefore the replaced element cannot be dragged + // anymore, this hack forces a rerender in order to apply the dragRef + useEffect(() => { + if (!isDraggingSibling) { + forceRerenderAfterDnd(prev => !prev); + } + }, [isDraggingSibling]); - // 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 && hoverClientX > hoverMiddleX) { - return; - } - // Dragging upwards - if (dragIndex < hoverIndex && hoverClientX < hoverMiddleX) { - return; - } + // Create the refs + // We need 1 for the drop target + // 1 for the drag target + const refs = { + dragRef: drag(dragRef), + dropRef: drop(dropRef), + }; - onMoveField(dragIndex, hoverIndex); + return ( + + {isDragging && } + {!isDragging && isDraggingSibling && } - item.index = hoverIndex; - }, - }); - - const [{ isDragging }, drag, preview] = useDrag({ - type: ItemTypes.FIELD, - item: () => { - return { index, labelField, name }; - }, - collect: monitor => ({ - isDragging: monitor.isDragging(), - }), - end: () => { - setIsDraggingSibling(false); - }, - }); - - useEffect(() => { - preview(getEmptyImage(), { captureDraggingState: false }); - }, [preview]); - - useEffect(() => { - if (isDragging) { - setIsDraggingSibling(true); - } - }, [isDragging, setIsDraggingSibling]); - - // Effect in order to force a rerender after reordering the components - // Since we are removing the Accordion when doing the DnD we are losing the dragRef, therefore the replaced element cannot be dragged - // anymore, this hack forces a rerender in order to apply the dragRef - useEffect(() => { - if (!isDraggingSibling) { - forceRerenderAfterDnd(prev => !prev); - } - }, [isDraggingSibling]); - - // Create the refs - // We need 1 for the drop target - // 1 for the drag target - const refs = { - dragRef: drag(dragRef), - dropRef: drop(dropRef), - }; - - return ( -
- - {isDragging && } - {!isDragging && isDraggingSibling && ( - - )} - - {!isDragging && !isDraggingSibling && ( - + + e.stopPropagation()} + ref={refs.dragRef} + type="button" > - - e.stopPropagation()} - ref={refs.dragRef} - type="button" - > - - - {cardEllipsisTitle} - - - { - e.stopPropagation(); - onClickEditField(name); - }} - aria-label={formatMessage( - { - id: getTrad('components.DraggableCard.edit.field'), - defaultMessage: 'Edit {item}', - }, - { item: name } - )} - type="button" - > - - - - - - - - )} - -
- ); - } -); + + + {cardEllipsisTitle} + + + { + e.stopPropagation(); + onClickEditField(name); + }} + aria-label={formatMessage( + { + id: getTrad('components.DraggableCard.edit.field'), + defaultMessage: 'Edit {item}', + }, + { item: name } + )} + type="button" + > + + + + + + +
+ )} +
+ ); +}; DraggableCard.propTypes = { index: PropTypes.number.isRequired, diff --git a/packages/core/admin/admin/src/content-manager/pages/ListSettingsView/components/SortDisplayedFields.js b/packages/core/admin/admin/src/content-manager/pages/ListSettingsView/components/SortDisplayedFields.js index e9aefccb71..0ebc87a380 100644 --- a/packages/core/admin/admin/src/content-manager/pages/ListSettingsView/components/SortDisplayedFields.js +++ b/packages/core/admin/admin/src/content-manager/pages/ListSettingsView/components/SortDisplayedFields.js @@ -1,6 +1,5 @@ import React, { useState, useRef, useEffect } from 'react'; import styled from 'styled-components'; -import last from 'lodash/last'; import { PropTypes } from 'prop-types'; import { useIntl } from 'react-intl'; import { Box } from '@strapi/design-system/Box'; @@ -37,21 +36,24 @@ const SortDisplayedFields = ({ }) => { const { formatMessage } = useIntl(); const [isDraggingSibling, setIsDraggingSibling] = useState(false); - const [fieldWasAdded, setFieldWasAdded] = useState(false); - const fieldRefMap = useRef([]); + const [lastAction, setLastAction] = useState(null); + const scrollableContainerRef = useRef(); - function handleAddField(field) { - onAddField(field); - setFieldWasAdded(true); + function handleAddField(...args) { + setLastAction('add'); + onAddField(...args); + } + + function handleRemoveField(...args) { + setLastAction('remove'); + onRemoveField(...args); } useEffect(() => { - if (fieldWasAdded) { - const lastField = last(fieldRefMap.current); - - lastField.scrollIntoView(); + if (lastAction === 'add' && scrollableContainerRef?.current) { + scrollableContainerRef.current.scrollLeft = scrollableContainerRef.current.scrollWidth; } - }, [displayedFields, fieldWasAdded]); + }, [displayedFields, lastAction]); return ( <> @@ -72,7 +74,7 @@ const SortDisplayedFields = ({ borderWidth="1px" hasRadius > - + {displayedFields.map((field, index) => ( onRemoveField(e, index)} + onRemoveField={e => handleRemoveField(e, index)} name={field} labelField={metadatas[field].list.label || field} setIsDraggingSibling={setIsDraggingSibling} - ref={el => fieldRefMap.current.push(el)} /> ))} From 73b063cdcb132c6038c78508e8884f6b61573c2e Mon Sep 17 00:00:00 2001 From: Gustav Hansen Date: Mon, 21 Feb 2022 16:36:31 +0100 Subject: [PATCH 4/4] chore: Update snapshot tests --- .../tests/__snapshots__/index.test.js.snap | 206 +++++++++--------- 1 file changed, 103 insertions(+), 103 deletions(-) diff --git a/packages/core/admin/admin/src/content-manager/pages/ListSettingsView/tests/__snapshots__/index.test.js.snap b/packages/core/admin/admin/src/content-manager/pages/ListSettingsView/tests/__snapshots__/index.test.js.snap index fb11d2e36d..c155a0d3f4 100644 --- a/packages/core/admin/admin/src/content-manager/pages/ListSettingsView/tests/__snapshots__/index.test.js.snap +++ b/packages/core/admin/admin/src/content-manager/pages/ListSettingsView/tests/__snapshots__/index.test.js.snap @@ -3412,109 +3412,6 @@ exports[`ADMIN | CM | LV | Configure the view should add field 1`] = `
-
-
-
- - - - - - - - - - - - - michka - -
-
- - -
-
-
@@ -3721,6 +3618,109 @@ exports[`ADMIN | CM | LV | Configure the view should add field 1`] = `
+
+
+
+ + + + + + + + + + + + + michka + +
+
+ + +
+
+