mirror of
https://github.com/strapi/strapi.git
synced 2025-09-25 16:29:34 +00:00
Merge branch 'main' into enhancement/axios-refactoring
This commit is contained in:
commit
70a08c6795
2
.github/workflows/contributor-doc.yml
vendored
2
.github/workflows/contributor-doc.yml
vendored
@ -21,7 +21,7 @@ permissions: {}
|
|||||||
jobs:
|
jobs:
|
||||||
deploy:
|
deploy:
|
||||||
permissions:
|
permissions:
|
||||||
contents: write # to push pages branch (peaceiris/actions-gh-pages)
|
contents: write # to push pages branch (peaceiris/actions-gh-pages)
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
name: github-pages
|
name: github-pages
|
||||||
|
2
.github/workflows/nightly.yml
vendored
2
.github/workflows/nightly.yml
vendored
@ -6,7 +6,7 @@ on:
|
|||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read # to fetch code (actions/checkout)
|
contents: read # to fetch code (actions/checkout)
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
publish:
|
publish:
|
||||||
|
2
.github/workflows/tests.yml
vendored
2
.github/workflows/tests.yml
vendored
@ -14,7 +14,7 @@ concurrency:
|
|||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read # to fetch code (actions/checkout)
|
contents: read # to fetch code (actions/checkout)
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
lint:
|
lint:
|
||||||
|
@ -5,7 +5,7 @@ import get from 'lodash/get';
|
|||||||
import omit from 'lodash/omit';
|
import omit from 'lodash/omit';
|
||||||
import take from 'lodash/take';
|
import take from 'lodash/take';
|
||||||
import isEqual from 'react-fast-compare';
|
import isEqual from 'react-fast-compare';
|
||||||
import { GenericInput, NotAllowedInput, useLibrary, useCustomFields } from '@strapi/helper-plugin';
|
import { GenericInput, NotAllowedInput, useLibrary } from '@strapi/helper-plugin';
|
||||||
import { useContentTypeLayout } from '../../hooks';
|
import { useContentTypeLayout } from '../../hooks';
|
||||||
import { getFieldName } from '../../utils';
|
import { getFieldName } from '../../utils';
|
||||||
import Wysiwyg from '../Wysiwyg';
|
import Wysiwyg from '../Wysiwyg';
|
||||||
@ -37,11 +37,11 @@ function Inputs({
|
|||||||
queryInfos,
|
queryInfos,
|
||||||
value,
|
value,
|
||||||
size,
|
size,
|
||||||
|
customFieldInputs,
|
||||||
}) {
|
}) {
|
||||||
const { fields } = useLibrary();
|
const { fields } = useLibrary();
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
const { contentType: currentContentTypeLayout } = useContentTypeLayout();
|
const { contentType: currentContentTypeLayout } = useContentTypeLayout();
|
||||||
const customFieldsRegistry = useCustomFields();
|
|
||||||
|
|
||||||
const disabled = useMemo(() => !get(metadatas, 'editable', true), [metadatas]);
|
const disabled = useMemo(() => !get(metadatas, 'editable', true), [metadatas]);
|
||||||
const { type, customField: customFieldUid } = fieldSchema;
|
const { type, customField: customFieldUid } = fieldSchema;
|
||||||
@ -194,19 +194,6 @@ function Inputs({
|
|||||||
return minutes % metadatas.step === 0 ? metadatas.step : step;
|
return minutes % metadatas.step === 0 ? metadatas.step : step;
|
||||||
}, [inputType, inputValue, metadatas.step, step]);
|
}, [inputType, inputValue, metadatas.step, step]);
|
||||||
|
|
||||||
// Memoize the component to avoid remounting it and losing state
|
|
||||||
const CustomFieldInput = useMemo(() => {
|
|
||||||
if (customFieldUid) {
|
|
||||||
const customField = customFieldsRegistry.get(customFieldUid);
|
|
||||||
const CustomFieldInput = React.lazy(customField.components.Input);
|
|
||||||
|
|
||||||
return CustomFieldInput;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Not a custom field, component won't be used
|
|
||||||
return null;
|
|
||||||
}, [customFieldUid, customFieldsRegistry]);
|
|
||||||
|
|
||||||
if (visible === false) {
|
if (visible === false) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -268,12 +255,9 @@ function Inputs({
|
|||||||
media: fields.media,
|
media: fields.media,
|
||||||
wysiwyg: Wysiwyg,
|
wysiwyg: Wysiwyg,
|
||||||
...fields,
|
...fields,
|
||||||
|
...customFieldInputs,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (customFieldUid) {
|
|
||||||
customInputs[customFieldUid] = CustomFieldInput;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<GenericInput
|
<GenericInput
|
||||||
attribute={fieldSchema}
|
attribute={fieldSchema}
|
||||||
@ -309,6 +293,7 @@ Inputs.defaultProps = {
|
|||||||
size: undefined,
|
size: undefined,
|
||||||
value: null,
|
value: null,
|
||||||
queryInfos: {},
|
queryInfos: {},
|
||||||
|
customFieldInputs: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
Inputs.propTypes = {
|
Inputs.propTypes = {
|
||||||
@ -330,6 +315,7 @@ Inputs.propTypes = {
|
|||||||
defaultParams: PropTypes.object,
|
defaultParams: PropTypes.object,
|
||||||
endPoint: PropTypes.string,
|
endPoint: PropTypes.string,
|
||||||
}),
|
}),
|
||||||
|
customFieldInputs: PropTypes.object,
|
||||||
};
|
};
|
||||||
|
|
||||||
const Memoized = memo(Inputs, isEqual);
|
const Memoized = memo(Inputs, isEqual);
|
||||||
|
@ -0,0 +1,44 @@
|
|||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { useCustomFields } from '@strapi/helper-plugin';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description
|
||||||
|
* A hook to lazy load custom field components
|
||||||
|
* @param {Array.<string>} componentUids - The uids to look up components
|
||||||
|
* @returns object
|
||||||
|
*/
|
||||||
|
const useLazyComponents = (componentUids) => {
|
||||||
|
const [lazyComponentStore, setLazyComponentStore] = useState({});
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const customFieldsRegistry = useCustomFields();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const lazyLoadComponents = async (uids, components) => {
|
||||||
|
const modules = await Promise.all(components);
|
||||||
|
|
||||||
|
uids.forEach((uid, index) => {
|
||||||
|
if (!Object.keys(lazyComponentStore).includes(uid)) {
|
||||||
|
setLazyComponentStore({ ...lazyComponentStore, [uid]: modules[index].default });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
if (componentUids.length) {
|
||||||
|
const componentPromises = componentUids.map((uid) => {
|
||||||
|
const customField = customFieldsRegistry.get(uid);
|
||||||
|
|
||||||
|
return customField.components.Input();
|
||||||
|
});
|
||||||
|
|
||||||
|
lazyLoadComponents(componentUids, componentPromises);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (componentUids.length === Object.keys(lazyComponentStore).length) {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
}, [componentUids, customFieldsRegistry, loading, lazyComponentStore]);
|
||||||
|
|
||||||
|
return { isLazyLoading: loading, lazyComponentStore };
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useLazyComponents;
|
@ -0,0 +1,50 @@
|
|||||||
|
import { renderHook } from '@testing-library/react-hooks';
|
||||||
|
import useLazyComponents from '../index';
|
||||||
|
|
||||||
|
const mockCustomField = {
|
||||||
|
name: 'color',
|
||||||
|
pluginId: 'mycustomfields',
|
||||||
|
type: 'text',
|
||||||
|
icon: jest.fn(),
|
||||||
|
intlLabel: {
|
||||||
|
id: 'mycustomfields.color.label',
|
||||||
|
defaultMessage: 'Color',
|
||||||
|
},
|
||||||
|
intlDescription: {
|
||||||
|
id: 'mycustomfields.color.description',
|
||||||
|
defaultMessage: 'Select any color',
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
Input: jest.fn().mockResolvedValue({ default: jest.fn() }),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
jest.mock('@strapi/helper-plugin', () => ({
|
||||||
|
useCustomFields: () => ({
|
||||||
|
get: jest.fn().mockReturnValue(mockCustomField),
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('useLazyComponents', () => {
|
||||||
|
it('lazy loads the components', async () => {
|
||||||
|
const { result, waitForNextUpdate } = renderHook(() =>
|
||||||
|
useLazyComponents(['plugin::test.test'])
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result.current).toEqual({ isLazyLoading: true, lazyComponentStore: {} });
|
||||||
|
|
||||||
|
await waitForNextUpdate();
|
||||||
|
|
||||||
|
expect(JSON.stringify(result.current)).toEqual(
|
||||||
|
JSON.stringify({
|
||||||
|
isLazyLoading: false,
|
||||||
|
lazyComponentStore: { 'plugin::test.test': jest.fn() },
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it('handles no components to load', async () => {
|
||||||
|
const { result } = renderHook(() => useLazyComponents([]));
|
||||||
|
|
||||||
|
expect(result.current).toEqual({ isLazyLoading: false, lazyComponentStore: {} });
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,13 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { AnErrorOccurred } from '@strapi/helper-plugin';
|
||||||
|
import { Box } from '@strapi/design-system/Box';
|
||||||
|
|
||||||
|
const ErrorFallback = () => {
|
||||||
|
return (
|
||||||
|
<Box padding={8}>
|
||||||
|
<AnErrorOccurred />
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ErrorFallback;
|
@ -3,7 +3,7 @@ import { Switch, Route } from 'react-router-dom';
|
|||||||
import { ErrorBoundary } from 'react-error-boundary';
|
import { ErrorBoundary } from 'react-error-boundary';
|
||||||
import { get } from 'lodash';
|
import { get } from 'lodash';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { ErrorFallback, LoadingIndicatorPage, CheckPagePermissions } from '@strapi/helper-plugin';
|
import { LoadingIndicatorPage, CheckPagePermissions } from '@strapi/helper-plugin';
|
||||||
import permissions from '../../../permissions';
|
import permissions from '../../../permissions';
|
||||||
import { ContentTypeLayoutContext } from '../../contexts';
|
import { ContentTypeLayoutContext } from '../../contexts';
|
||||||
import { useFetchContentTypeLayout } from '../../hooks';
|
import { useFetchContentTypeLayout } from '../../hooks';
|
||||||
@ -12,6 +12,7 @@ import EditViewLayoutManager from '../EditViewLayoutManager';
|
|||||||
import EditSettingsView from '../EditSettingsView';
|
import EditSettingsView from '../EditSettingsView';
|
||||||
import ListViewLayout from '../ListViewLayoutManager';
|
import ListViewLayout from '../ListViewLayoutManager';
|
||||||
import ListSettingsView from '../ListSettingsView';
|
import ListSettingsView from '../ListSettingsView';
|
||||||
|
import ErrorFallback from './components/ErrorFallback';
|
||||||
|
|
||||||
const cmPermissions = permissions.contentManager;
|
const cmPermissions = permissions.contentManager;
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ import { Grid, GridItem } from '@strapi/design-system/Grid';
|
|||||||
import Inputs from '../../../components/Inputs';
|
import Inputs from '../../../components/Inputs';
|
||||||
import FieldComponent from '../../../components/FieldComponent';
|
import FieldComponent from '../../../components/FieldComponent';
|
||||||
|
|
||||||
const GridRow = ({ columns }) => {
|
const GridRow = ({ columns, customFieldInputs }) => {
|
||||||
return (
|
return (
|
||||||
<Grid gap={4}>
|
<Grid gap={4}>
|
||||||
{columns.map(({ fieldSchema, labelAction, metadatas, name, size, queryInfos }) => {
|
{columns.map(({ fieldSchema, labelAction, metadatas, name, size, queryInfos }) => {
|
||||||
@ -41,6 +41,7 @@ const GridRow = ({ columns }) => {
|
|||||||
labelAction={labelAction}
|
labelAction={labelAction}
|
||||||
metadatas={metadatas}
|
metadatas={metadatas}
|
||||||
queryInfos={queryInfos}
|
queryInfos={queryInfos}
|
||||||
|
customFieldInputs={customFieldInputs}
|
||||||
/>
|
/>
|
||||||
</GridItem>
|
</GridItem>
|
||||||
);
|
);
|
||||||
@ -49,8 +50,13 @@ const GridRow = ({ columns }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
GridRow.defaultProps = {
|
||||||
|
customFieldInputs: {},
|
||||||
|
};
|
||||||
|
|
||||||
GridRow.propTypes = {
|
GridRow.propTypes = {
|
||||||
columns: PropTypes.array.isRequired,
|
columns: PropTypes.array.isRequired,
|
||||||
|
customFieldInputs: PropTypes.object,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default GridRow;
|
export default GridRow;
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import React, { Suspense, memo } from 'react';
|
import React, { memo } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import {
|
import {
|
||||||
CheckPermissions,
|
CheckPermissions,
|
||||||
LoadingIndicatorPage,
|
|
||||||
useTracking,
|
useTracking,
|
||||||
LinkButton,
|
LinkButton,
|
||||||
|
LoadingIndicatorPage,
|
||||||
} from '@strapi/helper-plugin';
|
} from '@strapi/helper-plugin';
|
||||||
import { useIntl } from 'react-intl';
|
import { useIntl } from 'react-intl';
|
||||||
import { ContentLayout } from '@strapi/design-system/Layout';
|
import { ContentLayout } from '@strapi/design-system/Layout';
|
||||||
@ -23,13 +23,14 @@ import CollectionTypeFormWrapper from '../../components/CollectionTypeFormWrappe
|
|||||||
import EditViewDataManagerProvider from '../../components/EditViewDataManagerProvider';
|
import EditViewDataManagerProvider from '../../components/EditViewDataManagerProvider';
|
||||||
import SingleTypeFormWrapper from '../../components/SingleTypeFormWrapper';
|
import SingleTypeFormWrapper from '../../components/SingleTypeFormWrapper';
|
||||||
import { getTrad } from '../../utils';
|
import { getTrad } from '../../utils';
|
||||||
|
import useLazyComponents from '../../hooks/useLazyComponents';
|
||||||
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 { getFieldsActionMatchingPermissions } from './utils';
|
import { getFieldsActionMatchingPermissions } from './utils';
|
||||||
import DeleteLink from './DeleteLink';
|
import DeleteLink from './DeleteLink';
|
||||||
import GridRow from './GridRow';
|
import GridRow from './GridRow';
|
||||||
import { selectCurrentLayout, selectAttributesLayout } from './selectors';
|
import { selectCurrentLayout, selectAttributesLayout, selectCustomFieldUids } 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 }];
|
||||||
@ -38,14 +39,18 @@ const ctbPermissions = [{ action: 'plugin::content-type-builder.read', subject:
|
|||||||
const EditView = ({ allowedActions, isSingleType, goBack, slug, id, origin, userPermissions }) => {
|
const EditView = ({ allowedActions, isSingleType, goBack, slug, id, origin, userPermissions }) => {
|
||||||
const { trackUsage } = useTracking();
|
const { trackUsage } = useTracking();
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
const { createActionAllowedFields, readActionAllowedFields, updateActionAllowedFields } =
|
|
||||||
getFieldsActionMatchingPermissions(userPermissions, slug);
|
|
||||||
|
|
||||||
const { layout, formattedContentTypeLayout } = useSelector((state) => ({
|
const { layout, formattedContentTypeLayout, customFieldUids } = useSelector((state) => ({
|
||||||
layout: selectCurrentLayout(state),
|
layout: selectCurrentLayout(state),
|
||||||
formattedContentTypeLayout: selectAttributesLayout(state),
|
formattedContentTypeLayout: selectAttributesLayout(state),
|
||||||
|
customFieldUids: selectCustomFieldUids(state),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
const { isLazyLoading, lazyComponentStore } = useLazyComponents(customFieldUids);
|
||||||
|
|
||||||
|
const { createActionAllowedFields, readActionAllowedFields, updateActionAllowedFields } =
|
||||||
|
getFieldsActionMatchingPermissions(userPermissions, slug);
|
||||||
|
|
||||||
const configurationPermissions = isSingleType
|
const configurationPermissions = isSingleType
|
||||||
? cmPermissions.singleTypesConfigurations
|
? cmPermissions.singleTypesConfigurations
|
||||||
: cmPermissions.collectionTypesConfigurations;
|
: cmPermissions.collectionTypesConfigurations;
|
||||||
@ -64,6 +69,10 @@ const EditView = ({ allowedActions, isSingleType, goBack, slug, id, origin, user
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (isLazyLoading) {
|
||||||
|
return <LoadingIndicatorPage />;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DataManagementWrapper allLayoutData={layout} slug={slug} id={id} origin={origin}>
|
<DataManagementWrapper allLayoutData={layout} slug={slug} id={id} origin={origin}>
|
||||||
{({
|
{({
|
||||||
@ -110,54 +119,56 @@ const EditView = ({ allowedActions, isSingleType, goBack, slug, id, origin, user
|
|||||||
<ContentLayout>
|
<ContentLayout>
|
||||||
<Grid gap={4}>
|
<Grid gap={4}>
|
||||||
<GridItem col={9} s={12}>
|
<GridItem col={9} s={12}>
|
||||||
<Suspense fallback={<LoadingIndicatorPage />}>
|
<Stack spacing={6}>
|
||||||
<Stack spacing={6}>
|
{formattedContentTypeLayout.map((row, index) => {
|
||||||
{formattedContentTypeLayout.map((row, index) => {
|
if (isDynamicZone(row)) {
|
||||||
if (isDynamicZone(row)) {
|
const {
|
||||||
const {
|
0: {
|
||||||
0: {
|
0: { name, fieldSchema, metadatas, labelAction },
|
||||||
0: { name, fieldSchema, metadatas, labelAction },
|
},
|
||||||
},
|
} = row;
|
||||||
} = row;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Box key={index}>
|
|
||||||
<Grid gap={4}>
|
|
||||||
<GridItem col={12} s={12} xs={12}>
|
|
||||||
<DynamicZone
|
|
||||||
name={name}
|
|
||||||
fieldSchema={fieldSchema}
|
|
||||||
labelAction={labelAction}
|
|
||||||
metadatas={metadatas}
|
|
||||||
/>
|
|
||||||
</GridItem>
|
|
||||||
</Grid>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box key={index}>
|
||||||
key={index}
|
<Grid gap={4}>
|
||||||
hasRadius
|
<GridItem col={12} s={12} xs={12}>
|
||||||
background="neutral0"
|
<DynamicZone
|
||||||
shadow="tableShadow"
|
name={name}
|
||||||
paddingLeft={6}
|
fieldSchema={fieldSchema}
|
||||||
paddingRight={6}
|
labelAction={labelAction}
|
||||||
paddingTop={6}
|
metadatas={metadatas}
|
||||||
paddingBottom={6}
|
/>
|
||||||
borderColor="neutral150"
|
</GridItem>
|
||||||
>
|
</Grid>
|
||||||
<Stack spacing={6}>
|
|
||||||
{row.map((grid, gridRowIndex) => (
|
|
||||||
<GridRow columns={grid} key={gridRowIndex} />
|
|
||||||
))}
|
|
||||||
</Stack>
|
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
})}
|
}
|
||||||
</Stack>
|
|
||||||
</Suspense>
|
return (
|
||||||
|
<Box
|
||||||
|
key={index}
|
||||||
|
hasRadius
|
||||||
|
background="neutral0"
|
||||||
|
shadow="tableShadow"
|
||||||
|
paddingLeft={6}
|
||||||
|
paddingRight={6}
|
||||||
|
paddingTop={6}
|
||||||
|
paddingBottom={6}
|
||||||
|
borderColor="neutral150"
|
||||||
|
>
|
||||||
|
<Stack spacing={6}>
|
||||||
|
{row.map((grid, gridRowIndex) => (
|
||||||
|
<GridRow
|
||||||
|
columns={grid}
|
||||||
|
customFieldInputs={lazyComponentStore}
|
||||||
|
key={gridRowIndex}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Stack>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Stack>
|
||||||
</GridItem>
|
</GridItem>
|
||||||
<GridItem col={3} s={12}>
|
<GridItem col={3} s={12}>
|
||||||
<Stack spacing={2}>
|
<Stack spacing={2}>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
import { createAttributesLayout } from './utils';
|
import { createAttributesLayout, getCustomFieldUidsFromLayout } from './utils';
|
||||||
|
|
||||||
const selectCurrentLayout = (state) => state['content-manager_editViewLayoutManager'].currentLayout;
|
const selectCurrentLayout = (state) => state['content-manager_editViewLayoutManager'].currentLayout;
|
||||||
|
|
||||||
@ -7,4 +7,8 @@ const selectAttributesLayout = createSelector(selectCurrentLayout, (layout) =>
|
|||||||
createAttributesLayout(layout?.contentType ?? {})
|
createAttributesLayout(layout?.contentType ?? {})
|
||||||
);
|
);
|
||||||
|
|
||||||
export { selectCurrentLayout, selectAttributesLayout };
|
const selectCustomFieldUids = createSelector(selectCurrentLayout, (layout) =>
|
||||||
|
getCustomFieldUidsFromLayout(layout)
|
||||||
|
);
|
||||||
|
|
||||||
|
export { selectCurrentLayout, selectAttributesLayout, selectCustomFieldUids };
|
||||||
|
@ -0,0 +1,18 @@
|
|||||||
|
const getCustomFieldUidsFromLayout = (layout) => {
|
||||||
|
if (!layout) return [];
|
||||||
|
// Get all the fields on the content-type and its components
|
||||||
|
const allFields = [
|
||||||
|
...layout.contentType.layouts.edit,
|
||||||
|
...Object.values(layout.components).flatMap((component) => component.layouts.edit),
|
||||||
|
].flat();
|
||||||
|
// Filter that down to custom fields and map the uids
|
||||||
|
const customFieldUids = allFields
|
||||||
|
.filter((field) => field.fieldSchema.customField)
|
||||||
|
.map((customField) => customField.fieldSchema.customField);
|
||||||
|
// Make sure the list is unique
|
||||||
|
const uniqueCustomFieldUids = [...new Set(customFieldUids)];
|
||||||
|
|
||||||
|
return uniqueCustomFieldUids;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default getCustomFieldUidsFromLayout;
|
@ -1,3 +1,4 @@
|
|||||||
// eslint-disable-next-line import/prefer-default-export
|
// eslint-disable-next-line import/prefer-default-export
|
||||||
export { default as createAttributesLayout } from './createAttributesLayout';
|
export { default as createAttributesLayout } from './createAttributesLayout';
|
||||||
export { default as getFieldsActionMatchingPermissions } from './getFieldsActionMatchingPermissions';
|
export { default as getFieldsActionMatchingPermissions } from './getFieldsActionMatchingPermissions';
|
||||||
|
export { default as getCustomFieldUidsFromLayout } from './getCustomFieldUidsFromLayout';
|
||||||
|
@ -0,0 +1,86 @@
|
|||||||
|
import getCustomFieldUidsFromLayout from '../getCustomFieldUidsFromLayout';
|
||||||
|
|
||||||
|
describe('CONTENT MANAGER | CONTAINERS | EditView | utils | getCustomFieldUidsFromLayout', () => {
|
||||||
|
it('gets a unique list of custom field uids on the content-type layout', () => {
|
||||||
|
const mockLayoutData = {
|
||||||
|
contentType: {
|
||||||
|
layouts: {
|
||||||
|
edit: [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
name: 'short_text',
|
||||||
|
size: 6,
|
||||||
|
fieldSchema: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
name: 'dynamiczone',
|
||||||
|
size: 12,
|
||||||
|
fieldSchema: {
|
||||||
|
type: 'dynamiczone',
|
||||||
|
components: ['basic.simple'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
name: 'custom_field_2',
|
||||||
|
size: 6,
|
||||||
|
fieldSchema: {
|
||||||
|
type: 'string',
|
||||||
|
customField: 'plugin::color-picker.color',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
name: 'custom_field_2',
|
||||||
|
size: 6,
|
||||||
|
fieldSchema: {
|
||||||
|
type: 'string',
|
||||||
|
customField: 'plugin::color-picker.color',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
'basic.simple': {
|
||||||
|
uid: 'basic.simple',
|
||||||
|
layouts: {
|
||||||
|
edit: [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
name: 'name',
|
||||||
|
size: 6,
|
||||||
|
fieldSchema: {
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
name: 'custom_field_3',
|
||||||
|
size: 6,
|
||||||
|
fieldSchema: {
|
||||||
|
type: 'string',
|
||||||
|
customField: 'plugin::test-plugin.test',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const expected = ['plugin::color-picker.color', 'plugin::test-plugin.test'];
|
||||||
|
|
||||||
|
expect(getCustomFieldUidsFromLayout(mockLayoutData)).toEqual(expected);
|
||||||
|
});
|
||||||
|
});
|
Loading…
x
Reference in New Issue
Block a user