mirror of
https://github.com/strapi/strapi.git
synced 2025-11-24 06:03:37 +00:00
Merge pull request #14881 from strapi/chore/refactor-edit-view
[Content manager] Refactor edit view
This commit is contained in:
commit
94cd474940
@ -0,0 +1,56 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { Grid, GridItem } from '@strapi/design-system/Grid';
|
||||||
|
import Inputs from '../../../components/Inputs';
|
||||||
|
import FieldComponent from '../../../components/FieldComponent';
|
||||||
|
|
||||||
|
const GridRow = ({ columns }) => {
|
||||||
|
return (
|
||||||
|
<Grid gap={4}>
|
||||||
|
{columns.map(({ fieldSchema, labelAction, metadatas, name, size, queryInfos }) => {
|
||||||
|
const isComponent = fieldSchema.type === 'component';
|
||||||
|
|
||||||
|
if (isComponent) {
|
||||||
|
const { component, max, min, repeatable = false, required = false } = fieldSchema;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<GridItem col={size} s={12} xs={12} key={component}>
|
||||||
|
<FieldComponent
|
||||||
|
componentUid={component}
|
||||||
|
labelAction={labelAction}
|
||||||
|
isRepeatable={repeatable}
|
||||||
|
intlLabel={{
|
||||||
|
id: metadatas.label,
|
||||||
|
defaultMessage: metadatas.label,
|
||||||
|
}}
|
||||||
|
max={max}
|
||||||
|
min={min}
|
||||||
|
name={name}
|
||||||
|
required={required}
|
||||||
|
/>
|
||||||
|
</GridItem>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<GridItem col={size} key={name} s={12} xs={12}>
|
||||||
|
<Inputs
|
||||||
|
size={size}
|
||||||
|
fieldSchema={fieldSchema}
|
||||||
|
keys={name}
|
||||||
|
labelAction={labelAction}
|
||||||
|
metadatas={metadatas}
|
||||||
|
queryInfos={queryInfos}
|
||||||
|
/>
|
||||||
|
</GridItem>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Grid>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
GridRow.propTypes = {
|
||||||
|
columns: PropTypes.array.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default GridRow;
|
||||||
@ -1,6 +1,6 @@
|
|||||||
import React, { Suspense, memo, useCallback, useMemo } from 'react';
|
import React, { Suspense, memo } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import get from 'lodash/get';
|
import { useSelector } from 'react-redux';
|
||||||
import {
|
import {
|
||||||
CheckPermissions,
|
CheckPermissions,
|
||||||
LoadingIndicatorPage,
|
LoadingIndicatorPage,
|
||||||
@ -18,8 +18,7 @@ import Pencil from '@strapi/icons/Pencil';
|
|||||||
import { InjectionZone } from '../../../shared/components';
|
import { InjectionZone } from '../../../shared/components';
|
||||||
import permissions from '../../../permissions';
|
import permissions from '../../../permissions';
|
||||||
import DynamicZone from '../../components/DynamicZone';
|
import DynamicZone from '../../components/DynamicZone';
|
||||||
import FieldComponent from '../../components/FieldComponent';
|
|
||||||
import Inputs from '../../components/Inputs';
|
|
||||||
import CollectionTypeFormWrapper from '../../components/CollectionTypeFormWrapper';
|
import CollectionTypeFormWrapper from '../../components/CollectionTypeFormWrapper';
|
||||||
import EditViewDataManagerProvider from '../../components/EditViewDataManagerProvider';
|
import EditViewDataManagerProvider from '../../components/EditViewDataManagerProvider';
|
||||||
import SingleTypeFormWrapper from '../../components/SingleTypeFormWrapper';
|
import SingleTypeFormWrapper from '../../components/SingleTypeFormWrapper';
|
||||||
@ -27,64 +26,43 @@ import { getTrad } from '../../utils';
|
|||||||
import DraftAndPublishBadge from './DraftAndPublishBadge';
|
import DraftAndPublishBadge from './DraftAndPublishBadge';
|
||||||
import Informations from './Informations';
|
import Informations from './Informations';
|
||||||
import Header from './Header';
|
import Header from './Header';
|
||||||
import { createAttributesLayout, getFieldsActionMatchingPermissions } from './utils';
|
import { getFieldsActionMatchingPermissions } from './utils';
|
||||||
import DeleteLink from './DeleteLink';
|
import DeleteLink from './DeleteLink';
|
||||||
|
import GridRow from './GridRow';
|
||||||
|
import { selectCurrentLayout, selectAttributesLayout } from './selectors';
|
||||||
|
|
||||||
const cmPermissions = permissions.contentManager;
|
const cmPermissions = permissions.contentManager;
|
||||||
const ctbPermissions = [{ action: 'plugin::content-type-builder.read', subject: null }];
|
const ctbPermissions = [{ action: 'plugin::content-type-builder.read', subject: null }];
|
||||||
|
|
||||||
/* eslint-disable react/no-array-index-key */
|
/* eslint-disable react/no-array-index-key */
|
||||||
const EditView = ({
|
const EditView = ({ allowedActions, isSingleType, goBack, slug, id, origin, userPermissions }) => {
|
||||||
allowedActions,
|
|
||||||
isSingleType,
|
|
||||||
goBack,
|
|
||||||
layout,
|
|
||||||
slug,
|
|
||||||
id,
|
|
||||||
origin,
|
|
||||||
userPermissions,
|
|
||||||
}) => {
|
|
||||||
const { trackUsage } = useTracking();
|
const { trackUsage } = useTracking();
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
const { createActionAllowedFields, readActionAllowedFields, updateActionAllowedFields } =
|
const { createActionAllowedFields, readActionAllowedFields, updateActionAllowedFields } =
|
||||||
useMemo(() => {
|
getFieldsActionMatchingPermissions(userPermissions, slug);
|
||||||
return getFieldsActionMatchingPermissions(userPermissions, slug);
|
|
||||||
}, [userPermissions, slug]);
|
|
||||||
|
|
||||||
const configurationPermissions = useMemo(() => {
|
const { layout, formattedContentTypeLayout } = useSelector((state) => ({
|
||||||
return isSingleType
|
layout: selectCurrentLayout(state),
|
||||||
|
formattedContentTypeLayout: selectAttributesLayout(state),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const configurationPermissions = isSingleType
|
||||||
? cmPermissions.singleTypesConfigurations
|
? cmPermissions.singleTypesConfigurations
|
||||||
: cmPermissions.collectionTypesConfigurations;
|
: cmPermissions.collectionTypesConfigurations;
|
||||||
}, [isSingleType]);
|
|
||||||
|
|
||||||
// // FIXME when changing the routing
|
// // FIXME when changing the routing
|
||||||
const configurationsURL = `/content-manager/${
|
const configurationsURL = `/content-manager/${
|
||||||
isSingleType ? 'singleType' : 'collectionType'
|
isSingleType ? 'singleType' : 'collectionType'
|
||||||
}/${slug}/configurations/edit`;
|
}/${slug}/configurations/edit`;
|
||||||
const currentContentTypeLayoutData = get(layout, ['contentType'], {});
|
|
||||||
|
|
||||||
const DataManagementWrapper = useMemo(
|
const DataManagementWrapper = isSingleType ? SingleTypeFormWrapper : CollectionTypeFormWrapper;
|
||||||
() => (isSingleType ? SingleTypeFormWrapper : CollectionTypeFormWrapper),
|
|
||||||
[isSingleType]
|
|
||||||
);
|
|
||||||
|
|
||||||
// Check if a block is a dynamic zone
|
// Check if a block is a dynamic zone
|
||||||
const isDynamicZone = useCallback((block) => {
|
const isDynamicZone = (block) => {
|
||||||
return block.every((subBlock) => {
|
return block.every((subBlock) => {
|
||||||
return subBlock.every((obj) => obj.fieldSchema.type === 'dynamiczone');
|
return subBlock.every((obj) => obj.fieldSchema.type === 'dynamiczone');
|
||||||
});
|
});
|
||||||
}, []);
|
};
|
||||||
|
|
||||||
const formattedContentTypeLayout = useMemo(() => {
|
|
||||||
if (!currentContentTypeLayoutData.layouts) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
return createAttributesLayout(
|
|
||||||
currentContentTypeLayoutData.layouts.edit,
|
|
||||||
currentContentTypeLayoutData.attributes
|
|
||||||
);
|
|
||||||
}, [currentContentTypeLayoutData]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DataManagementWrapper allLayoutData={layout} slug={slug} id={id} origin={origin}>
|
<DataManagementWrapper allLayoutData={layout} slug={slug} id={id} origin={origin}>
|
||||||
@ -171,65 +149,9 @@ const EditView = ({
|
|||||||
borderColor="neutral150"
|
borderColor="neutral150"
|
||||||
>
|
>
|
||||||
<Stack spacing={6}>
|
<Stack spacing={6}>
|
||||||
{row.map((grid, gridIndex) => {
|
{row.map((grid, gridRowIndex) => (
|
||||||
return (
|
<GridRow columns={grid} key={gridRowIndex} />
|
||||||
<Grid gap={4} key={gridIndex}>
|
))}
|
||||||
{grid.map(
|
|
||||||
({
|
|
||||||
fieldSchema,
|
|
||||||
labelAction,
|
|
||||||
metadatas,
|
|
||||||
name,
|
|
||||||
size,
|
|
||||||
queryInfos,
|
|
||||||
}) => {
|
|
||||||
const isComponent = fieldSchema.type === 'component';
|
|
||||||
|
|
||||||
if (isComponent) {
|
|
||||||
const {
|
|
||||||
component,
|
|
||||||
max,
|
|
||||||
min,
|
|
||||||
repeatable = false,
|
|
||||||
required = false,
|
|
||||||
} = fieldSchema;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<GridItem col={size} s={12} xs={12} key={component}>
|
|
||||||
<FieldComponent
|
|
||||||
componentUid={component}
|
|
||||||
labelAction={labelAction}
|
|
||||||
isRepeatable={repeatable}
|
|
||||||
intlLabel={{
|
|
||||||
id: metadatas.label,
|
|
||||||
defaultMessage: metadatas.label,
|
|
||||||
}}
|
|
||||||
max={max}
|
|
||||||
min={min}
|
|
||||||
name={name}
|
|
||||||
required={required}
|
|
||||||
/>
|
|
||||||
</GridItem>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<GridItem col={size} key={name} s={12} xs={12}>
|
|
||||||
<Inputs
|
|
||||||
size={size}
|
|
||||||
fieldSchema={fieldSchema}
|
|
||||||
keys={name}
|
|
||||||
labelAction={labelAction}
|
|
||||||
metadatas={metadatas}
|
|
||||||
queryInfos={queryInfos}
|
|
||||||
/>
|
|
||||||
</GridItem>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
)}
|
|
||||||
</Grid>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</Stack>
|
</Stack>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
@ -328,16 +250,6 @@ EditView.propTypes = {
|
|||||||
canCreate: PropTypes.bool.isRequired,
|
canCreate: PropTypes.bool.isRequired,
|
||||||
canDelete: PropTypes.bool.isRequired,
|
canDelete: PropTypes.bool.isRequired,
|
||||||
}).isRequired,
|
}).isRequired,
|
||||||
layout: PropTypes.shape({
|
|
||||||
components: PropTypes.object.isRequired,
|
|
||||||
contentType: PropTypes.shape({
|
|
||||||
uid: PropTypes.string.isRequired,
|
|
||||||
settings: PropTypes.object.isRequired,
|
|
||||||
metadatas: PropTypes.object.isRequired,
|
|
||||||
options: PropTypes.object.isRequired,
|
|
||||||
attributes: PropTypes.object.isRequired,
|
|
||||||
}).isRequired,
|
|
||||||
}).isRequired,
|
|
||||||
id: PropTypes.string,
|
id: PropTypes.string,
|
||||||
isSingleType: PropTypes.bool,
|
isSingleType: PropTypes.bool,
|
||||||
goBack: PropTypes.func.isRequired,
|
goBack: PropTypes.func.isRequired,
|
||||||
@ -346,7 +258,4 @@ EditView.propTypes = {
|
|||||||
userPermissions: PropTypes.array,
|
userPermissions: PropTypes.array,
|
||||||
};
|
};
|
||||||
|
|
||||||
export { EditView };
|
|
||||||
export default memo(EditView);
|
export default memo(EditView);
|
||||||
|
|
||||||
// export default () => 'TODO Edit view';
|
|
||||||
|
|||||||
@ -0,0 +1,10 @@
|
|||||||
|
import { createSelector } from 'reselect';
|
||||||
|
import { createAttributesLayout } from './utils';
|
||||||
|
|
||||||
|
const selectCurrentLayout = (state) => state['content-manager_editViewLayoutManager'].currentLayout;
|
||||||
|
|
||||||
|
const selectAttributesLayout = createSelector(selectCurrentLayout, (layout) =>
|
||||||
|
createAttributesLayout(layout?.contentType ?? {})
|
||||||
|
);
|
||||||
|
|
||||||
|
export { selectCurrentLayout, selectAttributesLayout };
|
||||||
@ -1,14 +1,19 @@
|
|||||||
import { get, isEmpty } from 'lodash';
|
import { get, isEmpty } from 'lodash';
|
||||||
// TODO: refacto this file to avoid eslint issues
|
|
||||||
/* eslint-disable no-restricted-syntax */
|
|
||||||
/* eslint-disable no-unused-vars */
|
|
||||||
|
|
||||||
const createAttributesLayout = (currentLayout, attributes) => {
|
const createAttributesLayout = (currentContentTypeLayoutData) => {
|
||||||
|
if (!currentContentTypeLayoutData.layouts) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentLayout = currentContentTypeLayoutData.layouts.edit;
|
||||||
|
const attributes = currentContentTypeLayoutData.attributes;
|
||||||
|
|
||||||
const getType = (name) => get(attributes, [name, 'type'], '');
|
const getType = (name) => get(attributes, [name, 'type'], '');
|
||||||
|
|
||||||
let currentRowIndex = 0;
|
let currentRowIndex = 0;
|
||||||
const newLayout = [];
|
const newLayout = [];
|
||||||
|
|
||||||
for (let row of currentLayout) {
|
currentLayout.forEach((row) => {
|
||||||
const hasDynamicZone = row.some(({ name }) => getType(name) === 'dynamiczone');
|
const hasDynamicZone = row.some(({ name }) => getType(name) === 'dynamiczone');
|
||||||
|
|
||||||
if (!newLayout[currentRowIndex]) {
|
if (!newLayout[currentRowIndex]) {
|
||||||
@ -27,7 +32,7 @@ const createAttributesLayout = (currentLayout, attributes) => {
|
|||||||
} else {
|
} else {
|
||||||
newLayout[currentRowIndex].push(row);
|
newLayout[currentRowIndex].push(row);
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
|
|
||||||
return newLayout.filter((arr) => arr.length > 0);
|
return newLayout.filter((arr) => arr.length > 0);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -27,6 +27,12 @@ describe('Content Manager | EditView | utils | createAttributesLayout', () => {
|
|||||||
[{ name: 'postal_code', size: 6 }],
|
[{ name: 'postal_code', size: 6 }],
|
||||||
[{ name: 'geolocation', size: 12 }],
|
[{ name: 'geolocation', size: 12 }],
|
||||||
];
|
];
|
||||||
|
const currentLayoutData = {
|
||||||
|
layouts: {
|
||||||
|
edit: currentLayout,
|
||||||
|
},
|
||||||
|
attributes,
|
||||||
|
};
|
||||||
const expected = [
|
const expected = [
|
||||||
[
|
[
|
||||||
[
|
[
|
||||||
@ -38,7 +44,7 @@ describe('Content Manager | EditView | utils | createAttributesLayout', () => {
|
|||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
|
||||||
expect(createAttributesLayout(currentLayout, attributes)).toEqual(expected);
|
expect(createAttributesLayout(currentLayoutData)).toEqual(expected);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Should return an array of size 2 if there is a dynamic zone at the end of the layout', () => {
|
it('Should return an array of size 2 if there is a dynamic zone at the end of the layout', () => {
|
||||||
@ -68,6 +74,12 @@ describe('Content Manager | EditView | utils | createAttributesLayout', () => {
|
|||||||
[{ name: 'geolocation', size: 12 }],
|
[{ name: 'geolocation', size: 12 }],
|
||||||
[{ name: 'dynamicZone1', size: 12 }],
|
[{ name: 'dynamicZone1', size: 12 }],
|
||||||
];
|
];
|
||||||
|
const currentLayoutData = {
|
||||||
|
layouts: {
|
||||||
|
edit: currentLayout,
|
||||||
|
},
|
||||||
|
attributes,
|
||||||
|
};
|
||||||
const expected = [
|
const expected = [
|
||||||
[
|
[
|
||||||
[
|
[
|
||||||
@ -80,7 +92,7 @@ describe('Content Manager | EditView | utils | createAttributesLayout', () => {
|
|||||||
[[{ name: 'dynamicZone1', size: 12 }]],
|
[[{ name: 'dynamicZone1', size: 12 }]],
|
||||||
];
|
];
|
||||||
|
|
||||||
expect(createAttributesLayout(currentLayout, attributes)).toEqual(expected);
|
expect(createAttributesLayout(currentLayoutData)).toEqual(expected);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Should return an array of size 2 if there is a dynamic zone at the beginning of the layout', () => {
|
it('Should return an array of size 2 if there is a dynamic zone at the beginning of the layout', () => {
|
||||||
@ -114,6 +126,12 @@ describe('Content Manager | EditView | utils | createAttributesLayout', () => {
|
|||||||
[{ name: 'postal_code', size: 6 }],
|
[{ name: 'postal_code', size: 6 }],
|
||||||
[{ name: 'geolocation', size: 12 }],
|
[{ name: 'geolocation', size: 12 }],
|
||||||
];
|
];
|
||||||
|
const currentLayoutData = {
|
||||||
|
layouts: {
|
||||||
|
edit: currentLayout,
|
||||||
|
},
|
||||||
|
attributes,
|
||||||
|
};
|
||||||
const expected = [
|
const expected = [
|
||||||
[[{ name: 'dynamicZone1', size: 12 }]],
|
[[{ name: 'dynamicZone1', size: 12 }]],
|
||||||
[
|
[
|
||||||
@ -126,7 +144,7 @@ describe('Content Manager | EditView | utils | createAttributesLayout', () => {
|
|||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
|
||||||
expect(createAttributesLayout(currentLayout, attributes)).toEqual(expected);
|
expect(createAttributesLayout(currentLayoutData)).toEqual(expected);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Should return an array of size 5 if there are 3 dynamic zones', () => {
|
it('Should return an array of size 5 if there are 3 dynamic zones', () => {
|
||||||
@ -164,6 +182,12 @@ describe('Content Manager | EditView | utils | createAttributesLayout', () => {
|
|||||||
[{ name: 'geolocation', size: 12 }],
|
[{ name: 'geolocation', size: 12 }],
|
||||||
[{ name: 'dynamicZone3', size: 12 }],
|
[{ name: 'dynamicZone3', size: 12 }],
|
||||||
];
|
];
|
||||||
|
const currentLayoutData = {
|
||||||
|
layouts: {
|
||||||
|
edit: currentLayout,
|
||||||
|
},
|
||||||
|
attributes,
|
||||||
|
};
|
||||||
const expected = [
|
const expected = [
|
||||||
[[{ name: 'dynamicZone1', size: 12 }]],
|
[[{ name: 'dynamicZone1', size: 12 }]],
|
||||||
[
|
[
|
||||||
@ -177,6 +201,11 @@ describe('Content Manager | EditView | utils | createAttributesLayout', () => {
|
|||||||
[[{ name: 'dynamicZone3', size: 12 }]],
|
[[{ name: 'dynamicZone3', size: 12 }]],
|
||||||
];
|
];
|
||||||
|
|
||||||
expect(createAttributesLayout(currentLayout, attributes)).toEqual(expected);
|
expect(createAttributesLayout(currentLayoutData)).toEqual(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should return an empty array when no layouts are found', () => {
|
||||||
|
const currentLayoutData = {};
|
||||||
|
expect(createAttributesLayout(currentLayoutData)).toEqual([]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -30,7 +30,7 @@ const EditViewLayoutManager = ({ layout, ...rest }) => {
|
|||||||
return <LoadingIndicatorPage />;
|
return <LoadingIndicatorPage />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return <Permissions {...rest} layout={currentLayout} userPermissions={permissions} />;
|
return <Permissions {...rest} userPermissions={permissions} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
EditViewLayoutManager.propTypes = {
|
EditViewLayoutManager.propTypes = {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user