Merge pull request #11679 from strapi/configure/edit-view

Configure/edit view
This commit is contained in:
ELABBASSI Hicham 2021-11-25 17:03:23 +01:00 committed by GitHub
commit f74c9ddf20
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 1993 additions and 1137 deletions

View File

@ -54,7 +54,9 @@ const CustomDragLayer = () => {
<LayoutDndProvider>
<div style={layerStyles}>
<div style={getItemStyles(initialOffset, currentOffset, mouseOffset)} className="col-md-2">
{itemType === ItemTypes.FIELD && <CardPreview labelField={item.labelField} />}
{[ItemTypes.EDIT_RELATION, ItemTypes.EDIT_FIELD].includes(itemType) && (
<CardPreview labelField={item.labelField} />
)}
{itemType === ItemTypes.COMPONENT && (
<RepeatableComponentPreview displayedValue={item.displayedValue} />
)}

View File

@ -1,5 +1,6 @@
import React, { memo, useMemo } from 'react';
import PropTypes from 'prop-types';
import { useIntl } from 'react-intl';
import get from 'lodash/get';
import omit from 'lodash/omit';
import take from 'lodash/take';
@ -36,6 +37,7 @@ function Inputs({
value,
}) {
const { fields } = useLibrary();
const { formatMessage } = useIntl();
const { contentType: currentContentTypeLayout } = useContentTypeLayout();
const disabled = useMemo(() => !get(metadatas, 'editable', true), [metadatas]);
@ -187,10 +189,14 @@ function Inputs({
<SelectWrapper
{...metadatas}
{...fieldSchema}
description={{
id: metadatas.description,
defaultMessage: metadatas.description,
}}
description={
metadatas.description
? formatMessage({
id: metadatas.description,
defaultMessage: metadatas.description,
})
: undefined
}
intlLabel={{
id: metadatas.label,
defaultMessage: metadatas.label,

View File

@ -0,0 +1,393 @@
import React, { useRef, useEffect, useState } from 'react';
import styled from 'styled-components';
import PropTypes from 'prop-types';
import { useDrop, useDrag } from 'react-dnd';
import { getEmptyImage } from 'react-dnd-html5-backend';
import { Flex } from '@strapi/design-system/Flex';
import { Box } from '@strapi/design-system/Box';
import { GridItem } from '@strapi/design-system/Grid';
import Drag from '@strapi/icons/Drag';
import { ItemTypes } from '../../../utils';
import FieldButtonContent from './FieldButtonContent';
import { useLayoutDnd } from '../../../hooks';
const Wrapper = styled(Flex)`
position: relative;
${({ isFirst, isLast, hasHorizontalPadding }) => {
if (isFirst) {
return `
padding-right: 4px;
`;
}
if (isLast) {
return `
padding-left: 4px;
`;
}
if (hasHorizontalPadding) {
return `
padding: 0 4px;
`;
}
return '';
}}
${({ showRightCarret, showLeftCarret, theme }) => {
if (showRightCarret) {
return `
&:after {
content: '';
position: absolute;
right: -1px;
background-color: ${theme.colors.primary600};
width: 2px;
height: 100%;
align-self: stretch;
z-index: 1;
}
`;
}
if (showLeftCarret) {
return `
&:before {
content: '';
position: absolute;
left: -1px;
background-color: ${theme.colors.primary600};
width: 2px;
height: 100%;
align-self: stretch;
z-index: 1;
}
`;
}
return '';
}};
`;
const CustomDragIcon = styled(Drag)`
height: ${12 / 16}rem;
width: ${12 / 16}rem;
path {
fill: ${({ theme }) => theme.colors.neutral600};
}
`;
const CustomFlex = styled(Flex)`
display: ${({ dragStart }) => (dragStart ? 'none' : 'flex')};
opacity: ${({ isDragging, isFullSize, isHidden }) => {
if (isDragging && !isFullSize) {
return 0.2;
}
if ((isDragging && isFullSize) || isHidden) {
return 0;
}
return 1;
}};
`;
const DragButton = styled(Flex)`
cursor: all-scroll;
border-right: 1px solid ${({ theme }) => theme.colors.neutral200};
`;
const DisplayedFieldButton = ({
attribute,
children,
index,
lastIndex,
moveItem,
moveRow,
name,
onDeleteField,
onEditField,
rowIndex,
size,
}) => {
const [dragStart, setDragStart] = useState(false);
const isHidden = name === '_TEMP_';
const { setIsDraggingSibling } = useLayoutDnd();
const isFullSize = size === 12;
const dragRef = useRef(null);
const dropRef = useRef(null);
const [{ clientOffset, isOver }, drop] = useDrop({
accept: ItemTypes.EDIT_FIELD,
hover(item, monitor) {
if (!dropRef.current) {
return;
}
// We use the hover only to reorder full size items
if (item.size !== 12) {
return;
}
const dragIndex = monitor.getItem().index;
const hoverIndex = index;
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 = 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 (dragRow < targetRow && hoverClientY < hoverMiddleY) {
return;
}
// Dragging upwards
if (dragRow > targetRow && hoverClientY > hoverMiddleY) {
return;
}
moveRow(dragRow, targetRow);
item.rowIndex = targetRow;
item.itemIndex = hoverIndex;
},
drop(item, monitor) {
if (!dropRef.current) {
return;
}
const dragIndex = monitor.getItem().index;
const hoverIndex = index;
const dragRow = monitor.getItem().rowIndex;
const targetRow = rowIndex;
// Don't reorder on drop for full size elements since it is already done in the hover
if (item.size === 12) {
return;
}
// Don't replace item with themselves
if (dragIndex === hoverIndex && dragRow === targetRow) {
return;
}
// Determine rectangle on screen
const hoverBoundingRect = dropRef.current.getBoundingClientRect();
// Scroll window if mouse near vertical edge(100px)
// Horizontal Check --
if (
Math.abs(monitor.getClientOffset().x - hoverBoundingRect.left) >
hoverBoundingRect.width / 1.8
) {
moveItem(dragIndex, hoverIndex + 1, dragRow, targetRow);
item.itemIndex = hoverIndex + 1;
item.rowIndex = targetRow;
return;
}
// Vertical Check |
// Time to actually perform the action
moveItem(dragIndex, hoverIndex, 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.itemIndex = hoverIndex;
item.rowIndex = targetRow;
},
collect: monitor => ({
canDrop: monitor.canDrop(),
clientOffset: monitor.getClientOffset(),
isOver: monitor.isOver(),
isOverCurrent: monitor.isOver({ shallow: true }),
itemType: monitor.getItemType(),
}),
});
const [{ isDragging, getItem }, drag, dragPreview] = useDrag({
type: ItemTypes.EDIT_FIELD,
item: () => {
setIsDraggingSibling(true);
return {
index,
labelField: name,
rowIndex,
name,
size,
};
},
canDrag() {
// Each row of the layout has a max size of 12 (based on bootstrap grid system)
// So in order to offer a better drop zone we add the _TEMP_ div to complete the remaining substract (12 - existing)
// Those divs cannot be dragged
// If we wanted to offer the ability to create new lines in the layout (which will come later)
// We will need to add a 12 size _TEMP_ div to offer a drop target between each existing row.
return name !== '_TEMP_';
},
collect: monitor => ({
isDragging: monitor.isDragging(),
getItem: monitor.getItem(),
}),
end: () => {
setIsDraggingSibling(false);
},
});
// Remove the default preview when the item is being dragged
// The preview is handled by the DragLayer
useEffect(() => {
dragPreview(getEmptyImage(), { captureDraggingState: true });
}, [dragPreview]);
// Create the refs
// We need 1 for the drop target
// 1 for the drag target
const refs = {
dragRef: drag(dragRef),
dropRef: drop(dropRef),
};
let showLeftCarret = false;
let showRightCarret = false;
if (dropRef.current && clientOffset) {
const hoverBoundingRect = dropRef.current.getBoundingClientRect();
showLeftCarret =
isOver &&
getItem.size !== 12 &&
Math.abs(clientOffset.x - hoverBoundingRect.left) < hoverBoundingRect.width / 2;
showRightCarret =
isOver &&
getItem.size !== 12 &&
Math.abs(clientOffset.x - hoverBoundingRect.left) > hoverBoundingRect.width / 2;
if (name === '_TEMP_') {
showLeftCarret = isOver && getItem.size !== 12;
showRightCarret = false;
}
}
const getHeight = () => {
if (attribute && isFullSize) {
return `${74 / 16}rem`;
}
return `${32 / 16}rem`;
};
const isFirst = index === 0 && !isFullSize;
const isLast = index === lastIndex && !isFullSize;
const hasHorizontalPadding = index !== 0 && !isFullSize;
return (
<GridItem col={size}>
<Wrapper
ref={refs.dropRef}
showLeftCarret={showLeftCarret}
showRightCarret={showRightCarret}
isFirst={isFirst}
isLast={isLast}
hasHorizontalPadding={hasHorizontalPadding}
onDrag={() => {
if (isFullSize && !dragStart) {
setDragStart(true);
}
}}
onDragEnd={() => {
if (isFullSize) {
setDragStart(false);
}
}}
>
{dragStart && isFullSize && (
<Box
// style={{ display: isDragging ? 'block' : 'none' }}
width="100%"
height="2px"
background="primary600"
/>
)}
<CustomFlex
width={isFullSize && dragStart ? 0 : '100%'}
borderColor="neutral150"
hasRadius
background="neutral100"
minHeight={getHeight()}
alignItems="stretch"
isDragging={isDragging}
dragStart={dragStart}
isFullSize={isFullSize}
isHidden={isHidden}
>
<DragButton
as="button"
type="button"
ref={refs.dragRef}
onClick={e => e.stopPropagation()}
alignItems="center"
paddingLeft={3}
paddingRight={3}
// Disable the keyboard navigation since the drag n drop isn't accessible with the keyboard for the moment
tabIndex={-1}
>
<CustomDragIcon />
</DragButton>
{!isHidden && (
<FieldButtonContent
attribute={attribute}
onEditField={onEditField}
onDeleteField={onDeleteField}
>
{children}
</FieldButtonContent>
)}
</CustomFlex>
</Wrapper>
</GridItem>
);
};
DisplayedFieldButton.defaultProps = {
attribute: undefined,
};
DisplayedFieldButton.propTypes = {
attribute: PropTypes.shape({
components: PropTypes.array,
component: PropTypes.string,
type: PropTypes.string,
}),
children: PropTypes.string.isRequired,
index: PropTypes.number.isRequired,
moveItem: PropTypes.func.isRequired,
moveRow: PropTypes.func.isRequired,
name: PropTypes.string.isRequired,
onDeleteField: PropTypes.func.isRequired,
onEditField: PropTypes.func.isRequired,
rowIndex: PropTypes.number.isRequired,
lastIndex: PropTypes.number.isRequired,
size: PropTypes.number.isRequired,
};
export default DisplayedFieldButton;

View File

@ -1,24 +1,19 @@
import React from 'react';
import PropTypes from 'prop-types';
import { useIntl } from 'react-intl';
import get from 'lodash/get';
import { Button } from '@strapi/design-system/Button';
import { Box } from '@strapi/design-system/Box';
import { Typography } from '@strapi/design-system/Typography';
import { Grid, GridItem } from '@strapi/design-system/Grid';
import { Stack } from '@strapi/design-system/Stack';
import { Flex } from '@strapi/design-system/Flex';
import { VisuallyHidden } from '@strapi/design-system/VisuallyHidden';
import { SimpleMenu, MenuItem } from '@strapi/design-system/SimpleMenu';
import Plus from '@strapi/icons/Plus';
import { getTrad } from '../../../utils';
import { useLayoutDnd } from '../../../hooks';
import FieldButton from './FieldButton';
import RowsLayout from './RowsLayout';
import LinkToCTB from './LinkToCTB';
const DisplayedFields = ({ editLayout, editLayoutRemainingFields, onRemoveField, onAddField }) => {
const { formatMessage } = useIntl();
const { setEditFieldToSelect, attributes, modifiedData } = useLayoutDnd();
return (
<Stack size={4}>
@ -32,47 +27,23 @@ const DisplayedFields = ({ editLayout, editLayoutRemainingFields, onRemoveField,
})}
</Typography>
</Box>
{/* Since the drag n drop will not be available, this text will be hidden for the moment */}
{/* <Box>
<Box>
<Typography variant="pi" textColor="neutral600">
{formatMessage({
id: 'containers.SettingPage.editSettings.description',
defaultMessage: 'Drag & drop the fields to build the layout',
})}
</Typography>
</Box> */}
</Box>
</div>
<LinkToCTB />
</Flex>
<Box padding={4} hasRadius borderStyle="dashed" borderWidth="1px" borderColor="neutral300">
<Stack size={2}>
{editLayout.map(row => (
<Grid gap={4} key={row.rowId}>
{row.rowContent.map((rowItem, index) => {
const attribute = get(attributes, [rowItem.name], {});
const attributeLabel = get(
modifiedData,
['metadatas', rowItem.name, 'edit', 'label'],
''
);
return (
<GridItem key={rowItem.name} col={rowItem.size}>
{rowItem.name !== '_TEMP_' ? (
<FieldButton
onEditField={() => setEditFieldToSelect(rowItem.name)}
onDeleteField={() => onRemoveField(row.rowId, index)}
attribute={attribute}
>
{attributeLabel || rowItem.name}
</FieldButton>
) : (
<VisuallyHidden />
)}
</GridItem>
);
})}
</Grid>
{editLayout.map((row, index) => (
<React.Fragment key={row.rowId}>
<RowsLayout row={row} rowIndex={index} onRemoveField={onRemoveField} />
</React.Fragment>
))}
<SimpleMenu
id="label"

View File

@ -1,118 +0,0 @@
import React from 'react';
import styled from 'styled-components';
import PropTypes from 'prop-types';
import { Box } from '@strapi/design-system/Box';
import { Flex } from '@strapi/design-system/Flex';
import { IconButton } from '@strapi/design-system/IconButton';
import { Typography } from '@strapi/design-system/Typography';
import Drag from '@strapi/icons/Drag';
import Pencil from '@strapi/icons/Pencil';
import Trash from '@strapi/icons/Trash';
import { useIntl } from 'react-intl';
import ComponentFieldList from './ComponentFieldList';
import DynamicZoneList from './DynamicZoneList';
import getTrad from '../../../utils/getTrad';
const CustomIconButton = styled(IconButton)`
background-color: transparent;
path {
fill: ${({ theme }) => theme.colors.neutral600};
}
`;
const CustomDragIcon = styled(Drag)`
height: ${12 / 16}rem;
width: ${12 / 16}rem;
path {
fill: ${({ theme }) => theme.colors.neutral600};
}
`;
const CustomFlex = styled(Flex)`
border-right: 1px solid ${({ theme }) => theme.colors.neutral200};
`;
const FieldButton = ({ attribute, onEditField, onDeleteField, children }) => {
const { formatMessage } = useIntl();
const getHeight = () => {
const higherFields = ['json', 'text', 'file', 'media', 'component', 'richtext', 'dynamiczone'];
if (attribute && higherFields.includes(attribute.type)) {
return `${74 / 16}rem`;
}
return `${32 / 16}rem`;
};
return (
<Flex
width="100%"
borderColor="neutral150"
hasRadius
background="neutral100"
minHeight={getHeight()}
alignItems="stretch"
>
<CustomFlex alignItems="center" paddingLeft={3} paddingRight={3}>
<CustomDragIcon />
</CustomFlex>
<Box overflow="hidden" width="100%">
<Flex paddingLeft={3} alignItems="baseline" justifyContent="space-between">
<Box>
<Typography fontWeight="bold" textColor="neutral800">
{children}
</Typography>
</Box>
<Flex>
<CustomIconButton
label={formatMessage(
{
id: getTrad('containers.ListSettingsView.modal-form.edit-label'),
defaultMessage: `Edit {fieldName}`,
},
{ fieldName: children }
)}
onClick={onEditField}
icon={<Pencil />}
noBorder
/>
<CustomIconButton
label={formatMessage(
{
id: getTrad('app.component.table.delete'),
defaultMessage: `Delete {target}`,
},
{
target: children,
}
)}
data-testid="delete-field"
onClick={onDeleteField}
icon={<Trash />}
noBorder
/>
</Flex>
</Flex>
{attribute?.type === 'component' && (
<ComponentFieldList componentUid={attribute.component} />
)}
{attribute?.type === 'dynamiczone' && <DynamicZoneList components={attribute.components} />}
</Box>
</Flex>
);
};
FieldButton.defaultProps = {
attribute: undefined,
};
FieldButton.propTypes = {
attribute: PropTypes.shape({
components: PropTypes.array,
component: PropTypes.string,
type: PropTypes.string,
}),
onEditField: PropTypes.func.isRequired,
onDeleteField: PropTypes.func.isRequired,
children: PropTypes.string.isRequired,
};
export default FieldButton;

View File

@ -0,0 +1,84 @@
import React from 'react';
import styled from 'styled-components';
import PropTypes from 'prop-types';
import { useIntl } from 'react-intl';
import { Box } from '@strapi/design-system/Box';
import { Flex } from '@strapi/design-system/Flex';
import { IconButton } from '@strapi/design-system/IconButton';
import { Text } from '@strapi/design-system/Text';
import Pencil from '@strapi/icons/Pencil';
import Trash from '@strapi/icons/Trash';
import { getTrad } from '../../../utils';
import ComponentFieldList from './ComponentFieldList';
import DynamicZoneList from './DynamicZoneList';
const CustomIconButton = styled(IconButton)`
background-color: transparent;
path {
fill: ${({ theme }) => theme.colors.neutral600};
}
`;
const FieldButtonContent = ({ attribute, onEditField, onDeleteField, children }) => {
const { formatMessage } = useIntl();
return (
<Box overflow="hidden" width="100%">
<Flex paddingLeft={3} alignItems="baseline" justifyContent="space-between">
<Box>
<Text textColor="neutral800" bold>
{children}
</Text>
</Box>
<Flex>
<CustomIconButton
label={formatMessage(
{
id: getTrad('containers.ListSettingsView.modal-form.edit-label'),
defaultMessage: `Edit {fieldName}`,
},
{ fieldName: children }
)}
onClick={onEditField}
icon={<Pencil />}
noBorder
/>
<CustomIconButton
label={formatMessage(
{
id: getTrad('app.component.table.delete'),
defaultMessage: `Delete {target}`,
},
{
target: children,
}
)}
data-testid="delete-field"
onClick={onDeleteField}
icon={<Trash />}
noBorder
/>
</Flex>
</Flex>
{attribute?.type === 'component' && <ComponentFieldList componentUid={attribute.component} />}
{attribute?.type === 'dynamiczone' && <DynamicZoneList components={attribute.components} />}
</Box>
);
};
FieldButtonContent.defaultProps = {
attribute: undefined,
};
FieldButtonContent.propTypes = {
attribute: PropTypes.shape({
components: PropTypes.array,
component: PropTypes.string,
type: PropTypes.string,
}),
onEditField: PropTypes.func.isRequired,
onDeleteField: PropTypes.func.isRequired,
children: PropTypes.string.isRequired,
};
export default FieldButtonContent;

View File

@ -0,0 +1,135 @@
import React, { useRef, useEffect } from 'react';
import styled from 'styled-components';
import PropTypes from 'prop-types';
import { useDrop, useDrag } from 'react-dnd';
import { getEmptyImage } from 'react-dnd-html5-backend';
import { Flex } from '@strapi/design-system/Flex';
import Drag from '@strapi/icons/Drag';
import { ItemTypes } from '../../../utils';
import FieldButtonContent from './FieldButtonContent';
const CustomDragIcon = styled(Drag)`
height: ${12 / 16}rem;
width: ${12 / 16}rem;
path {
fill: ${({ theme }) => theme.colors.neutral600};
}
`;
const CustomFlex = styled(Flex)`
opacity: ${({ isDragging }) => (isDragging ? 0 : 1)};
`;
const DragButton = styled(Flex)`
cursor: all-scroll;
border-right: 1px solid ${({ theme }) => theme.colors.neutral200};
`;
const RelationalFieldButton = ({
attribute,
onEditField,
onDeleteField,
children,
index,
name,
onMoveField,
}) => {
const dragButtonRef = useRef();
const [, drop] = useDrop({
accept: ItemTypes.EDIT_RELATION,
hover(item) {
if (!dragButtonRef.current) {
return;
}
const dragIndex = item.index;
const hoverIndex = index;
// Don't replace items with themselves
if (dragIndex === hoverIndex) {
return;
}
onMoveField(dragIndex, hoverIndex);
item.index = hoverIndex;
},
});
const [{ isDragging }, drag, dragPreview] = useDrag({
type: ItemTypes.EDIT_RELATION,
item: () => {
return { index, labelField: children, name };
},
collect: monitor => ({
isDragging: monitor.isDragging(),
}),
});
useEffect(() => {
dragPreview(getEmptyImage(), { captureDraggingState: true });
}, [dragPreview]);
drag(drop(dragButtonRef));
const getHeight = () => {
const higherFields = ['json', 'text', 'file', 'media', 'component', 'richtext', 'dynamiczone'];
if (attribute && higherFields.includes(attribute.type)) {
return `${74 / 16}rem`;
}
return `${32 / 16}rem`;
};
return (
<CustomFlex
width="100%"
borderColor="neutral150"
hasRadius
background="neutral100"
minHeight={getHeight()}
alignItems="stretch"
isDragging={isDragging}
>
<DragButton
as="button"
type="button"
ref={dragButtonRef}
onClick={e => e.stopPropagation()}
alignItems="center"
paddingLeft={3}
paddingRight={3}
// Disable the keyboard navigation since the drag n drop isn't accessible with the keyboard for the moment
tabIndex={-1}
>
<CustomDragIcon />
</DragButton>
<FieldButtonContent
attribute={attribute}
onEditField={onEditField}
onDeleteField={onDeleteField}
>
{children}
</FieldButtonContent>
</CustomFlex>
);
};
RelationalFieldButton.defaultProps = {
attribute: undefined,
};
RelationalFieldButton.propTypes = {
attribute: PropTypes.shape({
components: PropTypes.array,
component: PropTypes.string,
type: PropTypes.string,
}),
onEditField: PropTypes.func.isRequired,
onDeleteField: PropTypes.func.isRequired,
children: PropTypes.string.isRequired,
index: PropTypes.number.isRequired,
name: PropTypes.string.isRequired,
onMoveField: PropTypes.func.isRequired,
};
export default RelationalFieldButton;

View File

@ -9,8 +9,8 @@ import { Stack } from '@strapi/design-system/Stack';
import { SimpleMenu, MenuItem } from '@strapi/design-system/SimpleMenu';
import Plus from '@strapi/icons/Plus';
import { getTrad } from '../../../utils';
import FieldButton from './FieldButton';
import { useLayoutDnd } from '../../../hooks';
import RelationalFieldButton from './RelationalFieldButton';
const RelationalFields = ({
relationsLayout,
@ -19,7 +19,7 @@ const RelationalFields = ({
onAddField,
}) => {
const { formatMessage } = useIntl();
const { setEditFieldToSelect, modifiedData } = useLayoutDnd();
const { setEditFieldToSelect, modifiedData, onMoveRelation } = useLayoutDnd();
return (
<Stack size={4}>
@ -32,15 +32,14 @@ const RelationalFields = ({
})}
</Typography>
</Box>
{/* Since the drag n drop will not be available, this text will be hidden for the moment */}
{/* <Box>
<Box>
<Typography variant="pi" textColor="neutral600">
{formatMessage({
id: 'containers.SettingPage.editSettings.description',
defaultMessage: 'Drag & drop the fields to build the layout',
})}
</Typography>
</Box> */}
</Box>
</div>
<Box padding={4} hasRadius borderStyle="dashed" borderWidth="1px" borderColor="neutral300">
<Stack size={2}>
@ -52,13 +51,16 @@ const RelationalFields = ({
);
return (
<FieldButton
<RelationalFieldButton
onEditField={() => setEditFieldToSelect(relationName)}
onDeleteField={() => onRemoveField(index)}
key={relationName}
index={index}
name={relationName}
onMoveField={onMoveRelation}
>
{relationLabel || relationName}
</FieldButton>
</RelationalFieldButton>
);
})}
<SimpleMenu

View File

@ -0,0 +1,39 @@
import React from 'react';
import PropTypes from 'prop-types';
import get from 'lodash/get';
import { useLayoutDnd } from '../../../hooks';
import DisplayedFieldButton from './DisplayedFieldButton';
const RowItemsLayout = ({ rowItem, onRemoveField, rowId, rowIndex, index, lastIndex }) => {
const { setEditFieldToSelect, attributes, modifiedData, moveRow, moveItem } = useLayoutDnd();
const attribute = get(attributes, [rowItem.name], {});
const attributeLabel = get(modifiedData, ['metadatas', rowItem.name, 'edit', 'label'], '');
return (
<DisplayedFieldButton
onEditField={() => setEditFieldToSelect(rowItem.name)}
onDeleteField={() => onRemoveField(rowId, index)}
attribute={attribute}
index={index}
lastIndex={lastIndex}
rowIndex={rowIndex}
name={rowItem.name}
size={rowItem.size}
moveRow={moveRow}
moveItem={moveItem}
>
{attributeLabel || rowItem.name}
</DisplayedFieldButton>
);
};
RowItemsLayout.propTypes = {
index: PropTypes.number.isRequired,
lastIndex: PropTypes.number.isRequired,
onRemoveField: PropTypes.func.isRequired,
rowId: PropTypes.number.isRequired,
rowIndex: PropTypes.number.isRequired,
rowItem: PropTypes.object.isRequired,
};
export default RowItemsLayout;

View File

@ -0,0 +1,32 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Grid } from '@strapi/design-system/Grid';
import RowItemsLayout from './RowItemsLayout';
const RowsLayout = ({ row, onRemoveField, rowIndex }) => {
return (
<Grid>
{row.rowContent.map((rowItem, index) => {
return (
<RowItemsLayout
key={rowItem.name}
rowItem={rowItem}
index={index}
rowId={row.rowId}
onRemoveField={onRemoveField}
rowIndex={rowIndex}
lastIndex={row.rowContent.length - 1}
/>
);
})}
</Grid>
);
};
RowsLayout.propTypes = {
onRemoveField: PropTypes.func.isRequired,
row: PropTypes.object.isRequired,
rowIndex: PropTypes.number.isRequired,
};
export default RowsLayout;

View File

@ -38,6 +38,7 @@ const EditSettingsView = ({ mainLayout, components, isContentTypeView, slug, upd
const [reducerState, dispatch] = useReducer(reducer, initialState, () =>
init(initialState, mainLayout, components)
);
const [isDraggingSibling, setIsDraggingSibling] = useState(false);
const { trackUsage } = useTracking();
const toggleNotification = useNotification();
const { goBack } = useHistory();
@ -151,6 +152,50 @@ const EditSettingsView = ({ mainLayout, components, isContentTypeView, slug, upd
submitMutation.mutate(body);
};
const handleMoveRelation = (fromIndex, toIndex) => {
dispatch({
type: 'MOVE_RELATION',
fromIndex,
toIndex,
});
};
const handleMoveField = (fromIndex, toIndex) => {
dispatch({
type: 'MOVE_FIELD',
fromIndex,
toIndex,
});
};
const moveItem = (dragIndex, hoverIndex, dragRowIndex, hoverRowIndex) => {
// Same row = just reorder
if (dragRowIndex === hoverRowIndex) {
dispatch({
type: 'REORDER_ROW',
dragRowIndex,
dragIndex,
hoverIndex,
});
} else {
dispatch({
type: 'REORDER_DIFF_ROW',
dragIndex,
hoverIndex,
dragRowIndex,
hoverRowIndex,
});
}
};
const moveRow = (fromIndex, toIndex) => {
dispatch({
type: 'MOVE_ROW',
fromIndex,
toIndex,
});
};
return (
<LayoutDndProvider
isContentTypeView={isContentTypeView}
@ -160,6 +205,10 @@ const EditSettingsView = ({ mainLayout, components, isContentTypeView, slug, upd
componentLayouts={componentLayouts}
selectedField={metaToEdit}
fieldForm={metaForm}
onMoveRelation={handleMoveRelation}
onMoveField={handleMoveField}
moveRow={moveRow}
moveItem={moveItem}
setEditFieldToSelect={name => {
dispatch({
type: 'SET_FIELD_TO_EDIT',
@ -167,6 +216,8 @@ const EditSettingsView = ({ mainLayout, components, isContentTypeView, slug, upd
});
handleToggleModal();
}}
isDraggingSibling={isDraggingSibling}
setIsDraggingSibling={setIsDraggingSibling}
>
<Main>
<Helmet

View File

@ -4,6 +4,8 @@ import { Router } from 'react-router-dom';
import { createMemoryHistory } from 'history';
import { IntlProvider } from 'react-intl';
import { QueryClient, QueryClientProvider } from 'react-query';
import { DndProvider } from 'react-dnd';
import { HTML5Backend } from 'react-dnd-html5-backend';
import { ThemeProvider } from '@strapi/design-system/ThemeProvider';
import { lightTheme } from '@strapi/design-system/themes';
import EditSettingsView from '../index';
@ -57,12 +59,14 @@ const makeApp = (history, layout) => {
<QueryClientProvider client={client}>
<IntlProvider messages={{ en: {} }} textComponent="span" locale="en">
<ThemeProvider theme={lightTheme}>
<EditSettingsView
mainLayout={layout || mainLayout}
components={components}
isContentTypeView
slug="api::address.address"
/>
<DndProvider backend={HTML5Backend}>
<EditSettingsView
mainLayout={layout || mainLayout}
components={components}
isContentTypeView
slug="api::address.address"
/>
</DndProvider>
</ThemeProvider>
</IntlProvider>
</QueryClientProvider>