@strapi/admin 4.5.1 → 4.5.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (139) hide show
  1. package/admin/src/components/LeftMenu/index.js +22 -4
  2. package/admin/src/content-manager/components/DynamicTable/CellContent/RelationMultiple/index.js +4 -3
  3. package/admin/src/content-manager/components/Inputs/index.js +5 -19
  4. package/admin/src/content-manager/hooks/useLazyComponents/index.js +44 -0
  5. package/admin/src/content-manager/pages/CollectionTypeRecursivePath/components/ErrorFallback.js +13 -0
  6. package/admin/src/content-manager/pages/CollectionTypeRecursivePath/index.js +2 -1
  7. package/admin/src/content-manager/pages/EditView/GridRow/index.js +62 -0
  8. package/admin/src/content-manager/pages/EditView/index.js +74 -154
  9. package/admin/src/content-manager/pages/EditView/selectors.js +14 -0
  10. package/admin/src/content-manager/pages/EditView/utils/createAttributesLayout.js +11 -6
  11. package/admin/src/content-manager/pages/EditView/utils/getCustomFieldUidsFromLayout.js +18 -0
  12. package/admin/src/content-manager/pages/EditView/utils/index.js +1 -0
  13. package/admin/src/content-manager/pages/EditViewLayoutManager/index.js +1 -1
  14. package/admin/src/content-manager/pages/ListSettingsView/components/Settings.js +79 -76
  15. package/admin/src/core/apis/CustomFields.js +46 -1
  16. package/admin/src/pages/MarketplacePage/components/SortSelect/index.js +20 -0
  17. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/components/FormApiTokenContainer/index.js +2 -2
  18. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/components/FormBody/index.js +1 -1
  19. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/components/FormHead/index.js +1 -1
  20. package/admin/src/translations/en.json +4 -0
  21. package/admin/src/translations/nl.json +210 -23
  22. package/admin/src/translations/pt.json +33 -32
  23. package/admin/src/translations/ru.json +789 -489
  24. package/admin/src/translations/zh.json +280 -95
  25. package/build/{7692.31e83caa.chunk.js → 1233.80b05d66.chunk.js} +144 -445
  26. package/build/1920.74a262e7.chunk.js +245 -0
  27. package/build/2438.61291207.chunk.js +2183 -0
  28. package/build/2517.9b4940f3.chunk.js +117 -0
  29. package/build/4306.f03c2b46.chunk.js +98 -0
  30. package/build/4318.7931eee7.chunk.js +30 -0
  31. package/build/4986.3820d11d.chunk.js +145 -0
  32. package/build/{8469.41c8d25f.chunk.js → 5015.f080b64e.chunk.js} +6 -1
  33. package/build/504.9aeff724.chunk.js +758 -0
  34. package/build/805.e991a370.chunk.js +138 -0
  35. package/build/8633.8da5488a.chunk.js +1 -0
  36. package/build/9707.a0cc4ad8.chunk.js +70 -0
  37. package/build/Admin-authenticatedApp.1e1d3bdd.chunk.js +80 -0
  38. package/build/Admin_InternalErrorPage.e0317a5e.chunk.js +1 -0
  39. package/build/Admin_homePage.54e33c2d.chunk.js +77 -0
  40. package/build/Admin_marketplace.8219fda6.chunk.js +26 -0
  41. package/build/Admin_pluginsPage.3c872de7.chunk.js +6 -0
  42. package/build/Admin_profilePage.e9fcce92.chunk.js +15 -0
  43. package/build/Admin_settingsPage.a1a5218b.chunk.js +178 -0
  44. package/build/admin-app.9cb0abc7.chunk.js +112 -0
  45. package/build/admin-edit-roles-page.23f15909.chunk.js +1 -0
  46. package/build/admin-edit-users.283b49ed.chunk.js +10 -0
  47. package/build/admin-users.a0748674.chunk.js +11 -0
  48. package/build/api-tokens-list-page.700e575f.chunk.js +16 -0
  49. package/build/content-manager.01e04e11.chunk.js +1200 -0
  50. package/build/content-type-builder-list-view.4412efc3.chunk.js +201 -0
  51. package/build/content-type-builder-translation-pt-json.96a31576.chunk.js +1 -0
  52. package/build/content-type-builder-translation-zh-json.3b0afd31.chunk.js +1 -0
  53. package/build/content-type-builder.848a9610.chunk.js +145 -0
  54. package/build/email-settings-page.d44a57cb.chunk.js +15 -0
  55. package/build/email-translation-pt-json.159505ab.chunk.js +1 -0
  56. package/build/email-translation-zh-json.62b1c6fe.chunk.js +1 -0
  57. package/build/en-json.7dd57947.chunk.js +1 -0
  58. package/build/i18n-settings-page.195d42fe.chunk.js +1 -0
  59. package/build/i18n-translation-zh-json.eeebb849.chunk.js +1 -0
  60. package/build/index.html +1 -1
  61. package/build/main.f31112a5.js +2034 -0
  62. package/build/nl-json.26f39180.chunk.js +1 -0
  63. package/build/pt-json.cd67ba86.chunk.js +1 -0
  64. package/build/ru-json.8830286f.chunk.js +1 -0
  65. package/build/runtime~main.f91e75cd.js +2 -0
  66. package/build/sso-settings-page.9f091262.chunk.js +1 -0
  67. package/build/upload-settings.450cab1a.chunk.js +18 -0
  68. package/build/upload-translation-pt-json.5c452b48.chunk.js +1 -0
  69. package/build/upload-translation-zh-json.ac5711de.chunk.js +1 -0
  70. package/build/upload.a73936d9.chunk.js +64 -0
  71. package/build/users-advanced-settings-page.dc23bc56.chunk.js +13 -0
  72. package/build/users-email-settings-page.6541d372.chunk.js +28 -0
  73. package/build/users-permissions-translation-zh-json.92f406f9.chunk.js +1 -0
  74. package/build/users-providers-settings-page.e11a2f64.chunk.js +33 -0
  75. package/build/users-roles-settings-page.445e5e16.chunk.js +30 -0
  76. package/build/webhook-edit-page.14ad1e6e.chunk.js +75 -0
  77. package/build/webhook-list-page.b87821f2.chunk.js +42 -0
  78. package/build/zh-json.2ecc6b99.chunk.js +1 -0
  79. package/jest.config.front.js +0 -1
  80. package/package.json +11 -10
  81. package/webpack.alias.js +3 -2
  82. package/webpack.config.js +13 -1
  83. package/build/1856.db9f5782.chunk.js +0 -174
  84. package/build/2077.fed8c9c3.chunk.js +0 -206
  85. package/build/2912.fccb2c43.chunk.js +0 -259
  86. package/build/4318.5e670740.chunk.js +0 -30
  87. package/build/4610.7614b003.chunk.js +0 -342
  88. package/build/4715.8e33d630.chunk.js +0 -387
  89. package/build/4800.a6935af6.chunk.js +0 -1
  90. package/build/4982.9e58ea3f.chunk.js +0 -325
  91. package/build/6925.bb6dd64d.chunk.js +0 -762
  92. package/build/7379.e972985f.chunk.js +0 -1
  93. package/build/7841.4804bd98.chunk.js +0 -259
  94. package/build/7866.6db2248d.chunk.js +0 -505
  95. package/build/8380.37126e0d.chunk.js +0 -299
  96. package/build/8549.5e5fb6b6.chunk.js +0 -159
  97. package/build/8738.5a02bffb.chunk.js +0 -463
  98. package/build/9066.5d980488.chunk.js +0 -101
  99. package/build/9420.7addc099.chunk.js +0 -505
  100. package/build/9649.b6afc945.chunk.js +0 -199
  101. package/build/Admin-authenticatedApp.c07d2a86.chunk.js +0 -80
  102. package/build/Admin_InternalErrorPage.12e24216.chunk.js +0 -1
  103. package/build/Admin_homePage.26d32e30.chunk.js +0 -72
  104. package/build/Admin_marketplace.444ff7b8.chunk.js +0 -22
  105. package/build/Admin_pluginsPage.4d59785a.chunk.js +0 -1
  106. package/build/Admin_profilePage.da32abbc.chunk.js +0 -15
  107. package/build/Admin_settingsPage.bf2234e1.chunk.js +0 -178
  108. package/build/admin-app.b157c10a.chunk.js +0 -112
  109. package/build/admin-edit-roles-page.69d9fcb2.chunk.js +0 -1
  110. package/build/admin-edit-users.c585212f.chunk.js +0 -10
  111. package/build/admin-users.d71f198a.chunk.js +0 -11
  112. package/build/api-tokens-list-page.bb36535f.chunk.js +0 -16
  113. package/build/content-manager.f38edbb6.chunk.js +0 -1202
  114. package/build/content-type-builder-list-view.5b3cd768.chunk.js +0 -194
  115. package/build/content-type-builder-translation-pt-json.766bd747.chunk.js +0 -1
  116. package/build/content-type-builder-translation-zh-json.2cc55621.chunk.js +0 -1
  117. package/build/content-type-builder.16af63a6.chunk.js +0 -145
  118. package/build/email-settings-page.91c925a5.chunk.js +0 -103
  119. package/build/email-translation-pt-json.959ea070.chunk.js +0 -1
  120. package/build/email-translation-zh-json.3455468b.chunk.js +0 -1
  121. package/build/en-json.4a269f6b.chunk.js +0 -1
  122. package/build/i18n-settings-page.4ef64441.chunk.js +0 -101
  123. package/build/main.ca8b0ee3.js +0 -9465
  124. package/build/nl-json.2b8cc3a0.chunk.js +0 -1
  125. package/build/pt-json.3161ca22.chunk.js +0 -1
  126. package/build/ru-json.d7cfc2ff.chunk.js +0 -1
  127. package/build/runtime~main.ede9da1e.js +0 -2
  128. package/build/sso-settings-page.9ceb0140.chunk.js +0 -1
  129. package/build/upload-settings.3f7ad973.chunk.js +0 -101
  130. package/build/upload-translation-zh-json.ee8fba96.chunk.js +0 -1
  131. package/build/upload.7084cea6.chunk.js +0 -7
  132. package/build/users-advanced-settings-page.6a838320.chunk.js +0 -101
  133. package/build/users-email-settings-page.73c41236.chunk.js +0 -1
  134. package/build/users-permissions-translation-zh-json.e03ae2a4.chunk.js +0 -1
  135. package/build/users-providers-settings-page.f8e78537.chunk.js +0 -1
  136. package/build/users-roles-settings-page.b33ec5e5.chunk.js +0 -30
  137. package/build/webhook-edit-page.dc9442ce.chunk.js +0 -23
  138. package/build/webhook-list-page.a110c462.chunk.js +0 -134
  139. package/build/zh-json.608aaf24.chunk.js +0 -1
@@ -2,7 +2,7 @@ import React, { useRef, useState } from 'react';
2
2
  import styled from 'styled-components';
3
3
  import PropTypes from 'prop-types';
4
4
  import { useIntl } from 'react-intl';
5
- import { NavLink as RouterNavLink } from 'react-router-dom';
5
+ import { NavLink as RouterNavLink, useLocation } from 'react-router-dom';
6
6
  import { Divider } from '@strapi/design-system/Divider';
7
7
  import {
8
8
  MainNav,
@@ -19,7 +19,7 @@ import { Typography } from '@strapi/design-system/Typography';
19
19
  import { Stack } from '@strapi/design-system/Stack';
20
20
  import Write from '@strapi/icons/Write';
21
21
  import Exit from '@strapi/icons/Exit';
22
- import { auth, usePersistentState, useAppInfos } from '@strapi/helper-plugin';
22
+ import { auth, usePersistentState, useAppInfos, useTracking } from '@strapi/helper-plugin';
23
23
  import useConfigurations from '../../hooks/useConfigurations';
24
24
 
25
25
  const LinkUserWrapper = styled(Box)`
@@ -59,6 +59,8 @@ const LeftMenu = ({ generalSectionLinks, pluginsSectionLinks }) => {
59
59
  const [condensed, setCondensed] = usePersistentState('navbar-condensed', false);
60
60
  const { userDisplayName } = useAppInfos();
61
61
  const { formatMessage } = useIntl();
62
+ const { trackUsage } = useTracking();
63
+ const { pathname } = useLocation();
62
64
 
63
65
  const initials = userDisplayName
64
66
  .split(' ')
@@ -82,6 +84,10 @@ const LeftMenu = ({ generalSectionLinks, pluginsSectionLinks }) => {
82
84
  }
83
85
  };
84
86
 
87
+ const handleClickOnLink = (destination = null) => {
88
+ trackUsage('willNavigate', { from: pathname, to: destination });
89
+ };
90
+
85
91
  const menuTitle = formatMessage({
86
92
  id: 'app.components.LeftMenu.navbrand.title',
87
93
  defaultMessage: 'Strapi Dashboard',
@@ -110,7 +116,12 @@ const LeftMenu = ({ generalSectionLinks, pluginsSectionLinks }) => {
110
116
  <Divider />
111
117
 
112
118
  <NavSections>
113
- <NavLink as={RouterNavLink} to="/content-manager" icon={<Write />}>
119
+ <NavLink
120
+ as={RouterNavLink}
121
+ to="/content-manager"
122
+ icon={<Write />}
123
+ onClick={() => handleClickOnLink('/content-manager')}
124
+ >
114
125
  {formatMessage({ id: 'global.content-manager', defaultMessage: 'Content manager' })}
115
126
  </NavLink>
116
127
 
@@ -125,7 +136,13 @@ const LeftMenu = ({ generalSectionLinks, pluginsSectionLinks }) => {
125
136
  const Icon = link.icon;
126
137
 
127
138
  return (
128
- <NavLink as={RouterNavLink} to={link.to} key={link.to} icon={<Icon />}>
139
+ <NavLink
140
+ as={RouterNavLink}
141
+ to={link.to}
142
+ key={link.to}
143
+ icon={<Icon />}
144
+ onClick={() => handleClickOnLink(link.to)}
145
+ >
129
146
  {formatMessage(link.intlLabel)}
130
147
  </NavLink>
131
148
  );
@@ -152,6 +169,7 @@ const LeftMenu = ({ generalSectionLinks, pluginsSectionLinks }) => {
152
169
  to={link.to}
153
170
  key={link.to}
154
171
  icon={<LinkIcon />}
172
+ onClick={() => handleClickOnLink(link.to)}
155
173
  >
156
174
  {formatMessage(link.intlLabel)}
157
175
  </NavLink>
@@ -5,6 +5,7 @@ import { useIntl } from 'react-intl';
5
5
  import { Typography } from '@strapi/design-system/Typography';
6
6
  import { Box } from '@strapi/design-system/Box';
7
7
  import { Badge } from '@strapi/design-system/Badge';
8
+ import { Flex } from '@strapi/design-system/Flex';
8
9
  import { SimpleMenu, MenuItem } from '@strapi/design-system/SimpleMenu';
9
10
  import { Loader } from '@strapi/design-system/Loader';
10
11
  import styled from 'styled-components';
@@ -38,8 +39,8 @@ const RelationMultiple = ({ fieldSchema, metadatas, name, entityId, value, conte
38
39
  const [isOpen, setIsOpen] = useState(false);
39
40
 
40
41
  const Label = (
41
- <>
42
- <Badge>{value.count}</Badge>{' '}
42
+ <Flex gap={1} wrap="nowrap">
43
+ <Badge>{value.count}</Badge>
43
44
  {formatMessage(
44
45
  {
45
46
  id: 'content-manager.containers.ListPage.items',
@@ -47,7 +48,7 @@ const RelationMultiple = ({ fieldSchema, metadatas, name, entityId, value, conte
47
48
  },
48
49
  { number: value.count }
49
50
  )}
50
- </>
51
+ </Flex>
51
52
  );
52
53
 
53
54
  const notify = () => {
@@ -5,7 +5,7 @@ import get from 'lodash/get';
5
5
  import omit from 'lodash/omit';
6
6
  import take from 'lodash/take';
7
7
  import isEqual from 'react-fast-compare';
8
- import { GenericInput, NotAllowedInput, useLibrary, useCustomFields } from '@strapi/helper-plugin';
8
+ import { GenericInput, NotAllowedInput, useLibrary } from '@strapi/helper-plugin';
9
9
  import { useContentTypeLayout } from '../../hooks';
10
10
  import { getFieldName } from '../../utils';
11
11
  import Wysiwyg from '../Wysiwyg';
@@ -37,11 +37,11 @@ function Inputs({
37
37
  queryInfos,
38
38
  value,
39
39
  size,
40
+ customFieldInputs,
40
41
  }) {
41
42
  const { fields } = useLibrary();
42
43
  const { formatMessage } = useIntl();
43
44
  const { contentType: currentContentTypeLayout } = useContentTypeLayout();
44
- const customFieldsRegistry = useCustomFields();
45
45
 
46
46
  const disabled = useMemo(() => !get(metadatas, 'editable', true), [metadatas]);
47
47
  const { type, customField: customFieldUid } = fieldSchema;
@@ -194,19 +194,6 @@ function Inputs({
194
194
  return minutes % metadatas.step === 0 ? metadatas.step : step;
195
195
  }, [inputType, inputValue, metadatas.step, step]);
196
196
 
197
- // Memoize the component to avoid remounting it and losing state
198
- const CustomFieldInput = useMemo(() => {
199
- if (customFieldUid) {
200
- const customField = customFieldsRegistry.get(customFieldUid);
201
- const CustomFieldInput = React.lazy(customField.components.Input);
202
-
203
- return CustomFieldInput;
204
- }
205
-
206
- // Not a custom field, component won't be used
207
- return null;
208
- }, [customFieldUid, customFieldsRegistry]);
209
-
210
197
  if (visible === false) {
211
198
  return null;
212
199
  }
@@ -268,12 +255,9 @@ function Inputs({
268
255
  media: fields.media,
269
256
  wysiwyg: Wysiwyg,
270
257
  ...fields,
258
+ ...customFieldInputs,
271
259
  };
272
260
 
273
- if (customFieldUid) {
274
- customInputs[customFieldUid] = CustomFieldInput;
275
- }
276
-
277
261
  return (
278
262
  <GenericInput
279
263
  attribute={fieldSchema}
@@ -309,6 +293,7 @@ Inputs.defaultProps = {
309
293
  size: undefined,
310
294
  value: null,
311
295
  queryInfos: {},
296
+ customFieldInputs: {},
312
297
  };
313
298
 
314
299
  Inputs.propTypes = {
@@ -330,6 +315,7 @@ Inputs.propTypes = {
330
315
  defaultParams: PropTypes.object,
331
316
  endPoint: PropTypes.string,
332
317
  }),
318
+ customFieldInputs: PropTypes.object,
333
319
  };
334
320
 
335
321
  const Memoized = memo(Inputs, isEqual);
@@ -0,0 +1,44 @@
1
+ import { useEffect, useState } from 'react';
2
+ import { useCustomFields } from '@strapi/helper-plugin';
3
+
4
+ /**
5
+ * @description
6
+ * A hook to lazy load custom field components
7
+ * @param {Array.<string>} componentUids - The uids to look up components
8
+ * @returns object
9
+ */
10
+ const useLazyComponents = (componentUids) => {
11
+ const [lazyComponentStore, setLazyComponentStore] = useState({});
12
+ const [loading, setLoading] = useState(true);
13
+ const customFieldsRegistry = useCustomFields();
14
+
15
+ useEffect(() => {
16
+ const lazyLoadComponents = async (uids, components) => {
17
+ const modules = await Promise.all(components);
18
+
19
+ uids.forEach((uid, index) => {
20
+ if (!Object.keys(lazyComponentStore).includes(uid)) {
21
+ setLazyComponentStore({ ...lazyComponentStore, [uid]: modules[index].default });
22
+ }
23
+ });
24
+ };
25
+
26
+ if (componentUids.length) {
27
+ const componentPromises = componentUids.map((uid) => {
28
+ const customField = customFieldsRegistry.get(uid);
29
+
30
+ return customField.components.Input();
31
+ });
32
+
33
+ lazyLoadComponents(componentUids, componentPromises);
34
+ }
35
+
36
+ if (componentUids.length === Object.keys(lazyComponentStore).length) {
37
+ setLoading(false);
38
+ }
39
+ }, [componentUids, customFieldsRegistry, loading, lazyComponentStore]);
40
+
41
+ return { isLazyLoading: loading, lazyComponentStore };
42
+ };
43
+
44
+ export default useLazyComponents;
@@ -0,0 +1,13 @@
1
+ import React from 'react';
2
+ import { AnErrorOccurred } from '@strapi/helper-plugin';
3
+ import { Box } from '@strapi/design-system/Box';
4
+
5
+ const ErrorFallback = () => {
6
+ return (
7
+ <Box padding={8}>
8
+ <AnErrorOccurred />
9
+ </Box>
10
+ );
11
+ };
12
+
13
+ export default ErrorFallback;
@@ -3,7 +3,7 @@ import { Switch, Route } from 'react-router-dom';
3
3
  import { ErrorBoundary } from 'react-error-boundary';
4
4
  import { get } from 'lodash';
5
5
  import PropTypes from 'prop-types';
6
- import { ErrorFallback, LoadingIndicatorPage, CheckPagePermissions } from '@strapi/helper-plugin';
6
+ import { LoadingIndicatorPage, CheckPagePermissions } from '@strapi/helper-plugin';
7
7
  import permissions from '../../../permissions';
8
8
  import { ContentTypeLayoutContext } from '../../contexts';
9
9
  import { useFetchContentTypeLayout } from '../../hooks';
@@ -12,6 +12,7 @@ import EditViewLayoutManager from '../EditViewLayoutManager';
12
12
  import EditSettingsView from '../EditSettingsView';
13
13
  import ListViewLayout from '../ListViewLayoutManager';
14
14
  import ListSettingsView from '../ListSettingsView';
15
+ import ErrorFallback from './components/ErrorFallback';
15
16
 
16
17
  const cmPermissions = permissions.contentManager;
17
18
 
@@ -0,0 +1,62 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { Grid, GridItem } from '@strapi/design-system/Grid';
4
+ import Inputs from '../../../components/Inputs';
5
+ import FieldComponent from '../../../components/FieldComponent';
6
+
7
+ const GridRow = ({ columns, customFieldInputs }) => {
8
+ return (
9
+ <Grid gap={4}>
10
+ {columns.map(({ fieldSchema, labelAction, metadatas, name, size, queryInfos }) => {
11
+ const isComponent = fieldSchema.type === 'component';
12
+
13
+ if (isComponent) {
14
+ const { component, max, min, repeatable = false, required = false } = fieldSchema;
15
+
16
+ return (
17
+ <GridItem col={size} s={12} xs={12} key={component}>
18
+ <FieldComponent
19
+ componentUid={component}
20
+ labelAction={labelAction}
21
+ isRepeatable={repeatable}
22
+ intlLabel={{
23
+ id: metadatas.label,
24
+ defaultMessage: metadatas.label,
25
+ }}
26
+ max={max}
27
+ min={min}
28
+ name={name}
29
+ required={required}
30
+ />
31
+ </GridItem>
32
+ );
33
+ }
34
+
35
+ return (
36
+ <GridItem col={size} key={name} s={12} xs={12}>
37
+ <Inputs
38
+ size={size}
39
+ fieldSchema={fieldSchema}
40
+ keys={name}
41
+ labelAction={labelAction}
42
+ metadatas={metadatas}
43
+ queryInfos={queryInfos}
44
+ customFieldInputs={customFieldInputs}
45
+ />
46
+ </GridItem>
47
+ );
48
+ })}
49
+ </Grid>
50
+ );
51
+ };
52
+
53
+ GridRow.defaultProps = {
54
+ customFieldInputs: {},
55
+ };
56
+
57
+ GridRow.propTypes = {
58
+ columns: PropTypes.array.isRequired,
59
+ customFieldInputs: PropTypes.object,
60
+ };
61
+
62
+ export default GridRow;
@@ -1,11 +1,11 @@
1
- import React, { Suspense, memo, useCallback, useMemo } from 'react';
1
+ import React, { memo } from 'react';
2
2
  import PropTypes from 'prop-types';
3
- import get from 'lodash/get';
3
+ import { useSelector } from 'react-redux';
4
4
  import {
5
5
  CheckPermissions,
6
- LoadingIndicatorPage,
7
6
  useTracking,
8
7
  LinkButton,
8
+ LoadingIndicatorPage,
9
9
  } from '@strapi/helper-plugin';
10
10
  import { useIntl } from 'react-intl';
11
11
  import { ContentLayout } from '@strapi/design-system/Layout';
@@ -18,73 +18,60 @@ import Pencil from '@strapi/icons/Pencil';
18
18
  import { InjectionZone } from '../../../shared/components';
19
19
  import permissions from '../../../permissions';
20
20
  import DynamicZone from '../../components/DynamicZone';
21
- import FieldComponent from '../../components/FieldComponent';
22
- import Inputs from '../../components/Inputs';
21
+
23
22
  import CollectionTypeFormWrapper from '../../components/CollectionTypeFormWrapper';
24
23
  import EditViewDataManagerProvider from '../../components/EditViewDataManagerProvider';
25
24
  import SingleTypeFormWrapper from '../../components/SingleTypeFormWrapper';
26
25
  import { getTrad } from '../../utils';
26
+ import useLazyComponents from '../../hooks/useLazyComponents';
27
27
  import DraftAndPublishBadge from './DraftAndPublishBadge';
28
28
  import Informations from './Informations';
29
29
  import Header from './Header';
30
- import { createAttributesLayout, getFieldsActionMatchingPermissions } from './utils';
30
+ import { getFieldsActionMatchingPermissions } from './utils';
31
31
  import DeleteLink from './DeleteLink';
32
+ import GridRow from './GridRow';
33
+ import { selectCurrentLayout, selectAttributesLayout, selectCustomFieldUids } from './selectors';
32
34
 
33
35
  const cmPermissions = permissions.contentManager;
34
36
  const ctbPermissions = [{ action: 'plugin::content-type-builder.read', subject: null }];
35
37
 
36
38
  /* eslint-disable react/no-array-index-key */
37
- const EditView = ({
38
- allowedActions,
39
- isSingleType,
40
- goBack,
41
- layout,
42
- slug,
43
- id,
44
- origin,
45
- userPermissions,
46
- }) => {
39
+ const EditView = ({ allowedActions, isSingleType, goBack, slug, id, origin, userPermissions }) => {
47
40
  const { trackUsage } = useTracking();
48
41
  const { formatMessage } = useIntl();
42
+
43
+ const { layout, formattedContentTypeLayout, customFieldUids } = useSelector((state) => ({
44
+ layout: selectCurrentLayout(state),
45
+ formattedContentTypeLayout: selectAttributesLayout(state),
46
+ customFieldUids: selectCustomFieldUids(state),
47
+ }));
48
+
49
+ const { isLazyLoading, lazyComponentStore } = useLazyComponents(customFieldUids);
50
+
49
51
  const { createActionAllowedFields, readActionAllowedFields, updateActionAllowedFields } =
50
- useMemo(() => {
51
- return getFieldsActionMatchingPermissions(userPermissions, slug);
52
- }, [userPermissions, slug]);
52
+ getFieldsActionMatchingPermissions(userPermissions, slug);
53
53
 
54
- const configurationPermissions = useMemo(() => {
55
- return isSingleType
56
- ? cmPermissions.singleTypesConfigurations
57
- : cmPermissions.collectionTypesConfigurations;
58
- }, [isSingleType]);
54
+ const configurationPermissions = isSingleType
55
+ ? cmPermissions.singleTypesConfigurations
56
+ : cmPermissions.collectionTypesConfigurations;
59
57
 
60
58
  // // FIXME when changing the routing
61
59
  const configurationsURL = `/content-manager/${
62
60
  isSingleType ? 'singleType' : 'collectionType'
63
61
  }/${slug}/configurations/edit`;
64
- const currentContentTypeLayoutData = get(layout, ['contentType'], {});
65
62
 
66
- const DataManagementWrapper = useMemo(
67
- () => (isSingleType ? SingleTypeFormWrapper : CollectionTypeFormWrapper),
68
- [isSingleType]
69
- );
63
+ const DataManagementWrapper = isSingleType ? SingleTypeFormWrapper : CollectionTypeFormWrapper;
70
64
 
71
65
  // Check if a block is a dynamic zone
72
- const isDynamicZone = useCallback((block) => {
66
+ const isDynamicZone = (block) => {
73
67
  return block.every((subBlock) => {
74
68
  return subBlock.every((obj) => obj.fieldSchema.type === 'dynamiczone');
75
69
  });
76
- }, []);
70
+ };
77
71
 
78
- const formattedContentTypeLayout = useMemo(() => {
79
- if (!currentContentTypeLayoutData.layouts) {
80
- return [];
81
- }
82
-
83
- return createAttributesLayout(
84
- currentContentTypeLayoutData.layouts.edit,
85
- currentContentTypeLayoutData.attributes
86
- );
87
- }, [currentContentTypeLayoutData]);
72
+ if (isLazyLoading) {
73
+ return <LoadingIndicatorPage />;
74
+ }
88
75
 
89
76
  return (
90
77
  <DataManagementWrapper allLayoutData={layout} slug={slug} id={id} origin={origin}>
@@ -132,110 +119,56 @@ const EditView = ({
132
119
  <ContentLayout>
133
120
  <Grid gap={4}>
134
121
  <GridItem col={9} s={12}>
135
- <Suspense fallback={<LoadingIndicatorPage />}>
136
- <Stack spacing={6}>
137
- {formattedContentTypeLayout.map((row, index) => {
138
- if (isDynamicZone(row)) {
139
- const {
140
- 0: {
141
- 0: { name, fieldSchema, metadatas, labelAction },
142
- },
143
- } = row;
144
-
145
- return (
146
- <Box key={index}>
147
- <Grid gap={4}>
148
- <GridItem col={12} s={12} xs={12}>
149
- <DynamicZone
150
- name={name}
151
- fieldSchema={fieldSchema}
152
- labelAction={labelAction}
153
- metadatas={metadatas}
154
- />
155
- </GridItem>
156
- </Grid>
157
- </Box>
158
- );
159
- }
122
+ <Stack spacing={6}>
123
+ {formattedContentTypeLayout.map((row, index) => {
124
+ if (isDynamicZone(row)) {
125
+ const {
126
+ 0: {
127
+ 0: { name, fieldSchema, metadatas, labelAction },
128
+ },
129
+ } = row;
160
130
 
161
131
  return (
162
- <Box
163
- key={index}
164
- hasRadius
165
- background="neutral0"
166
- shadow="tableShadow"
167
- paddingLeft={6}
168
- paddingRight={6}
169
- paddingTop={6}
170
- paddingBottom={6}
171
- borderColor="neutral150"
172
- >
173
- <Stack spacing={6}>
174
- {row.map((grid, gridIndex) => {
175
- return (
176
- <Grid gap={4} key={gridIndex}>
177
- {grid.map(
178
- ({
179
- fieldSchema,
180
- labelAction,
181
- metadatas,
182
- name,
183
- size,
184
- queryInfos,
185
- }) => {
186
- const isComponent = fieldSchema.type === 'component';
187
-
188
- if (isComponent) {
189
- const {
190
- component,
191
- max,
192
- min,
193
- repeatable = false,
194
- required = false,
195
- } = fieldSchema;
196
-
197
- return (
198
- <GridItem col={size} s={12} xs={12} key={component}>
199
- <FieldComponent
200
- componentUid={component}
201
- labelAction={labelAction}
202
- isRepeatable={repeatable}
203
- intlLabel={{
204
- id: metadatas.label,
205
- defaultMessage: metadatas.label,
206
- }}
207
- max={max}
208
- min={min}
209
- name={name}
210
- required={required}
211
- />
212
- </GridItem>
213
- );
214
- }
215
-
216
- return (
217
- <GridItem col={size} key={name} s={12} xs={12}>
218
- <Inputs
219
- size={size}
220
- fieldSchema={fieldSchema}
221
- keys={name}
222
- labelAction={labelAction}
223
- metadatas={metadatas}
224
- queryInfos={queryInfos}
225
- />
226
- </GridItem>
227
- );
228
- }
229
- )}
230
- </Grid>
231
- );
232
- })}
233
- </Stack>
132
+ <Box key={index}>
133
+ <Grid gap={4}>
134
+ <GridItem col={12} s={12} xs={12}>
135
+ <DynamicZone
136
+ name={name}
137
+ fieldSchema={fieldSchema}
138
+ labelAction={labelAction}
139
+ metadatas={metadatas}
140
+ />
141
+ </GridItem>
142
+ </Grid>
234
143
  </Box>
235
144
  );
236
- })}
237
- </Stack>
238
- </Suspense>
145
+ }
146
+
147
+ return (
148
+ <Box
149
+ key={index}
150
+ hasRadius
151
+ background="neutral0"
152
+ shadow="tableShadow"
153
+ paddingLeft={6}
154
+ paddingRight={6}
155
+ paddingTop={6}
156
+ paddingBottom={6}
157
+ borderColor="neutral150"
158
+ >
159
+ <Stack spacing={6}>
160
+ {row.map((grid, gridRowIndex) => (
161
+ <GridRow
162
+ columns={grid}
163
+ customFieldInputs={lazyComponentStore}
164
+ key={gridRowIndex}
165
+ />
166
+ ))}
167
+ </Stack>
168
+ </Box>
169
+ );
170
+ })}
171
+ </Stack>
239
172
  </GridItem>
240
173
  <GridItem col={3} s={12}>
241
174
  <Stack spacing={2}>
@@ -328,16 +261,6 @@ EditView.propTypes = {
328
261
  canCreate: PropTypes.bool.isRequired,
329
262
  canDelete: PropTypes.bool.isRequired,
330
263
  }).isRequired,
331
- layout: PropTypes.shape({
332
- components: PropTypes.object.isRequired,
333
- contentType: PropTypes.shape({
334
- uid: PropTypes.string.isRequired,
335
- settings: PropTypes.object.isRequired,
336
- metadatas: PropTypes.object.isRequired,
337
- options: PropTypes.object.isRequired,
338
- attributes: PropTypes.object.isRequired,
339
- }).isRequired,
340
- }).isRequired,
341
264
  id: PropTypes.string,
342
265
  isSingleType: PropTypes.bool,
343
266
  goBack: PropTypes.func.isRequired,
@@ -346,7 +269,4 @@ EditView.propTypes = {
346
269
  userPermissions: PropTypes.array,
347
270
  };
348
271
 
349
- export { EditView };
350
272
  export default memo(EditView);
351
-
352
- // export default () => 'TODO Edit view';
@@ -0,0 +1,14 @@
1
+ import { createSelector } from 'reselect';
2
+ import { createAttributesLayout, getCustomFieldUidsFromLayout } from './utils';
3
+
4
+ const selectCurrentLayout = (state) => state['content-manager_editViewLayoutManager'].currentLayout;
5
+
6
+ const selectAttributesLayout = createSelector(selectCurrentLayout, (layout) =>
7
+ createAttributesLayout(layout?.contentType ?? {})
8
+ );
9
+
10
+ const selectCustomFieldUids = createSelector(selectCurrentLayout, (layout) =>
11
+ getCustomFieldUidsFromLayout(layout)
12
+ );
13
+
14
+ export { selectCurrentLayout, selectAttributesLayout, selectCustomFieldUids };
@@ -1,14 +1,19 @@
1
1
  import { get, isEmpty } from 'lodash';
2
- // TODO: refacto this file to avoid eslint issues
3
- /* eslint-disable no-restricted-syntax */
4
- /* eslint-disable no-unused-vars */
5
2
 
6
- const createAttributesLayout = (currentLayout, attributes) => {
3
+ const createAttributesLayout = (currentContentTypeLayoutData) => {
4
+ if (!currentContentTypeLayoutData.layouts) {
5
+ return [];
6
+ }
7
+
8
+ const currentLayout = currentContentTypeLayoutData.layouts.edit;
9
+ const attributes = currentContentTypeLayoutData.attributes;
10
+
7
11
  const getType = (name) => get(attributes, [name, 'type'], '');
12
+
8
13
  let currentRowIndex = 0;
9
14
  const newLayout = [];
10
15
 
11
- for (let row of currentLayout) {
16
+ currentLayout.forEach((row) => {
12
17
  const hasDynamicZone = row.some(({ name }) => getType(name) === 'dynamiczone');
13
18
 
14
19
  if (!newLayout[currentRowIndex]) {
@@ -27,7 +32,7 @@ const createAttributesLayout = (currentLayout, attributes) => {
27
32
  } else {
28
33
  newLayout[currentRowIndex].push(row);
29
34
  }
30
- }
35
+ });
31
36
 
32
37
  return newLayout.filter((arr) => arr.length > 0);
33
38
  };
@@ -0,0 +1,18 @@
1
+ const getCustomFieldUidsFromLayout = (layout) => {
2
+ if (!layout) return [];
3
+ // Get all the fields on the content-type and its components
4
+ const allFields = [
5
+ ...layout.contentType.layouts.edit,
6
+ ...Object.values(layout.components).flatMap((component) => component.layouts.edit),
7
+ ].flat();
8
+ // Filter that down to custom fields and map the uids
9
+ const customFieldUids = allFields
10
+ .filter((field) => field.fieldSchema.customField)
11
+ .map((customField) => customField.fieldSchema.customField);
12
+ // Make sure the list is unique
13
+ const uniqueCustomFieldUids = [...new Set(customFieldUids)];
14
+
15
+ return uniqueCustomFieldUids;
16
+ };
17
+
18
+ export default getCustomFieldUidsFromLayout;