Skip to content

Commit

Permalink
Merge branch 'main' into fix/github-test-mysql5
Browse files Browse the repository at this point in the history
  • Loading branch information
petersg83 committed Nov 29, 2022
2 parents 78b8319 + 1ffef66 commit bbd318d
Show file tree
Hide file tree
Showing 12 changed files with 304 additions and 74 deletions.
Expand Up @@ -5,7 +5,7 @@ import get from 'lodash/get';
import omit from 'lodash/omit';
import take from 'lodash/take';
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 { getFieldName } from '../../utils';
import Wysiwyg from '../Wysiwyg';
Expand Down Expand Up @@ -37,11 +37,11 @@ function Inputs({
queryInfos,
value,
size,
customFieldInputs,
}) {
const { fields } = useLibrary();
const { formatMessage } = useIntl();
const { contentType: currentContentTypeLayout } = useContentTypeLayout();
const customFieldsRegistry = useCustomFields();

const disabled = useMemo(() => !get(metadatas, 'editable', true), [metadatas]);
const { type, customField: customFieldUid } = fieldSchema;
Expand Down Expand Up @@ -194,19 +194,6 @@ function Inputs({
return minutes % metadatas.step === 0 ? 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) {
return null;
}
Expand Down Expand Up @@ -268,12 +255,9 @@ function Inputs({
media: fields.media,
wysiwyg: Wysiwyg,
...fields,
...customFieldInputs,
};

if (customFieldUid) {
customInputs[customFieldUid] = CustomFieldInput;
}

return (
<GenericInput
attribute={fieldSchema}
Expand Down Expand Up @@ -309,6 +293,7 @@ Inputs.defaultProps = {
size: undefined,
value: null,
queryInfos: {},
customFieldInputs: {},
};

Inputs.propTypes = {
Expand All @@ -330,6 +315,7 @@ Inputs.propTypes = {
defaultParams: PropTypes.object,
endPoint: PropTypes.string,
}),
customFieldInputs: PropTypes.object,
};

const Memoized = memo(Inputs, isEqual);
Expand Down
@@ -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;
Expand Up @@ -3,7 +3,7 @@ import { Switch, Route } from 'react-router-dom';
import { ErrorBoundary } from 'react-error-boundary';
import { get } from 'lodash';
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 { ContentTypeLayoutContext } from '../../contexts';
import { useFetchContentTypeLayout } from '../../hooks';
Expand All @@ -12,6 +12,7 @@ import EditViewLayoutManager from '../EditViewLayoutManager';
import EditSettingsView from '../EditSettingsView';
import ListViewLayout from '../ListViewLayoutManager';
import ListSettingsView from '../ListSettingsView';
import ErrorFallback from './components/ErrorFallback';

const cmPermissions = permissions.contentManager;

Expand Down
Expand Up @@ -4,7 +4,7 @@ import { Grid, GridItem } from '@strapi/design-system/Grid';
import Inputs from '../../../components/Inputs';
import FieldComponent from '../../../components/FieldComponent';

const GridRow = ({ columns }) => {
const GridRow = ({ columns, customFieldInputs }) => {
return (
<Grid gap={4}>
{columns.map(({ fieldSchema, labelAction, metadatas, name, size, queryInfos }) => {
Expand Down Expand Up @@ -41,6 +41,7 @@ const GridRow = ({ columns }) => {
labelAction={labelAction}
metadatas={metadatas}
queryInfos={queryInfos}
customFieldInputs={customFieldInputs}
/>
</GridItem>
);
Expand All @@ -49,8 +50,13 @@ const GridRow = ({ columns }) => {
);
};

GridRow.defaultProps = {
customFieldInputs: {},
};

GridRow.propTypes = {
columns: PropTypes.array.isRequired,
customFieldInputs: PropTypes.object,
};

export default GridRow;
111 changes: 61 additions & 50 deletions packages/core/admin/admin/src/content-manager/pages/EditView/index.js
@@ -1,11 +1,11 @@
import React, { Suspense, memo } from 'react';
import React, { memo } from 'react';
import PropTypes from 'prop-types';
import { useSelector } from 'react-redux';
import {
CheckPermissions,
LoadingIndicatorPage,
useTracking,
LinkButton,
LoadingIndicatorPage,
} from '@strapi/helper-plugin';
import { useIntl } from 'react-intl';
import { ContentLayout } from '@strapi/design-system/Layout';
Expand All @@ -23,13 +23,14 @@ import CollectionTypeFormWrapper from '../../components/CollectionTypeFormWrappe
import EditViewDataManagerProvider from '../../components/EditViewDataManagerProvider';
import SingleTypeFormWrapper from '../../components/SingleTypeFormWrapper';
import { getTrad } from '../../utils';
import useLazyComponents from '../../hooks/useLazyComponents';
import DraftAndPublishBadge from './DraftAndPublishBadge';
import Informations from './Informations';
import Header from './Header';
import { getFieldsActionMatchingPermissions } from './utils';
import DeleteLink from './DeleteLink';
import GridRow from './GridRow';
import { selectCurrentLayout, selectAttributesLayout } from './selectors';
import { selectCurrentLayout, selectAttributesLayout, selectCustomFieldUids } from './selectors';

const cmPermissions = permissions.contentManager;
const ctbPermissions = [{ action: 'plugin::content-type-builder.read', subject: null }];
Expand All @@ -38,14 +39,18 @@ const ctbPermissions = [{ action: 'plugin::content-type-builder.read', subject:
const EditView = ({ allowedActions, isSingleType, goBack, slug, id, origin, userPermissions }) => {
const { trackUsage } = useTracking();
const { formatMessage } = useIntl();
const { createActionAllowedFields, readActionAllowedFields, updateActionAllowedFields } =
getFieldsActionMatchingPermissions(userPermissions, slug);

const { layout, formattedContentTypeLayout } = useSelector((state) => ({
const { layout, formattedContentTypeLayout, customFieldUids } = useSelector((state) => ({
layout: selectCurrentLayout(state),
formattedContentTypeLayout: selectAttributesLayout(state),
customFieldUids: selectCustomFieldUids(state),
}));

const { isLazyLoading, lazyComponentStore } = useLazyComponents(customFieldUids);

const { createActionAllowedFields, readActionAllowedFields, updateActionAllowedFields } =
getFieldsActionMatchingPermissions(userPermissions, slug);

const configurationPermissions = isSingleType
? cmPermissions.singleTypesConfigurations
: cmPermissions.collectionTypesConfigurations;
Expand All @@ -64,6 +69,10 @@ const EditView = ({ allowedActions, isSingleType, goBack, slug, id, origin, user
});
};

if (isLazyLoading) {
return <LoadingIndicatorPage />;
}

return (
<DataManagementWrapper allLayoutData={layout} slug={slug} id={id} origin={origin}>
{({
Expand Down Expand Up @@ -110,54 +119,56 @@ const EditView = ({ allowedActions, isSingleType, goBack, slug, id, origin, user
<ContentLayout>
<Grid gap={4}>
<GridItem col={9} s={12}>
<Suspense fallback={<LoadingIndicatorPage />}>
<Stack spacing={6}>
{formattedContentTypeLayout.map((row, index) => {
if (isDynamicZone(row)) {
const {
0: {
0: { name, fieldSchema, metadatas, labelAction },
},
} = 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>
);
}
<Stack spacing={6}>
{formattedContentTypeLayout.map((row, index) => {
if (isDynamicZone(row)) {
const {
0: {
0: { name, fieldSchema, metadatas, labelAction },
},
} = row;

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} key={gridRowIndex} />
))}
</Stack>
<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>
);
})}
</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 col={3} s={12}>
<Stack spacing={2}>
Expand Down

0 comments on commit bbd318d

Please sign in to comment.