@strapi/admin 4.6.0-alpha.1 → 4.6.0-beta.1

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 (248) hide show
  1. package/admin/src/components/AuthenticatedApp/index.js +13 -2
  2. package/admin/src/components/GlobalStyle/index.js +0 -5
  3. package/admin/src/content-manager/components/ComponentIcon/ComponentIcon.js +49 -0
  4. package/admin/src/content-manager/components/ComponentIcon/index.js +1 -0
  5. package/admin/src/content-manager/components/ComponentInitializer/index.js +1 -7
  6. package/admin/src/content-manager/components/{RepeatableComponent/DragPreview.js → DragLayer/ComponentDragPreview.js} +10 -11
  7. package/admin/src/content-manager/components/DragLayer/RelationDragPreview.js +75 -0
  8. package/admin/src/content-manager/components/DragLayer/index.js +23 -7
  9. package/admin/src/content-manager/components/DynamicZone/components/ComponentCard.js +17 -31
  10. package/admin/src/content-manager/components/DynamicZone/components/ComponentCategory.js +2 -2
  11. package/admin/src/content-manager/components/DynamicZone/components/DynamicComponent.js +129 -85
  12. package/admin/src/content-manager/components/DynamicZone/index.js +99 -24
  13. package/admin/src/content-manager/components/DynamicZone/utils/select.js +9 -5
  14. package/admin/src/content-manager/components/EditViewDataManagerProvider/index.js +76 -14
  15. package/admin/src/content-manager/components/EditViewDataManagerProvider/reducer.js +42 -25
  16. package/admin/src/content-manager/components/EditViewDataManagerProvider/utils/cleanData.js +24 -5
  17. package/admin/src/content-manager/components/EditViewDataManagerProvider/utils/recursivelyFindPathsBasedOnCondition.js +8 -1
  18. package/admin/src/content-manager/components/InputJSON/FieldWrapper.js +10 -2
  19. package/admin/src/content-manager/components/InputJSON/Label.js +2 -18
  20. package/admin/src/content-manager/components/InputJSON/index.js +7 -3
  21. package/admin/src/content-manager/components/NonRepeatableComponent/index.js +4 -0
  22. package/admin/src/content-manager/components/RelationInput/RelationInput.js +205 -74
  23. package/admin/src/content-manager/components/RelationInput/components/RelationItem.js +136 -21
  24. package/admin/src/content-manager/components/RelationInput/components/RelationList.js +1 -2
  25. package/admin/src/content-manager/components/RelationInput/constants.js +1 -0
  26. package/admin/src/content-manager/components/RelationInput/index.js +1 -0
  27. package/admin/src/content-manager/components/RelationInputDataManager/RelationInputDataManager.js +132 -10
  28. package/admin/src/content-manager/components/RepeatableComponent/components/Accordion.js +77 -0
  29. package/admin/src/content-manager/components/RepeatableComponent/components/Component.js +262 -0
  30. package/admin/src/content-manager/components/RepeatableComponent/{DraggedItem → components}/Preview.js +0 -0
  31. package/admin/src/content-manager/components/RepeatableComponent/index.js +148 -90
  32. package/admin/src/content-manager/components/RepeatableComponent/utils/getComponentErrorKeys.js +1 -1
  33. package/admin/src/content-manager/components/SingleTypeFormWrapper/index.js +1 -1
  34. package/admin/src/content-manager/components/Wysiwyg/Editor.js +1 -1
  35. package/admin/src/content-manager/hooks/index.js +2 -0
  36. package/admin/src/content-manager/hooks/useDragAndDrop.js +134 -0
  37. package/admin/src/content-manager/hooks/useKeyboardDragAndDrop.js +98 -0
  38. package/admin/src/content-manager/hooks/useLazyComponents/index.js +40 -15
  39. package/admin/src/content-manager/pages/EditSettingsView/components/DynamicZoneList.js +18 -38
  40. package/admin/src/content-manager/sharedReducers/crudReducer/actions.js +5 -0
  41. package/admin/src/content-manager/sharedReducers/crudReducer/constants.js +2 -0
  42. package/admin/src/content-manager/sharedReducers/crudReducer/reducer.js +7 -0
  43. package/admin/src/content-manager/utils/ItemTypes.js +1 -1
  44. package/admin/src/content-manager/utils/composeRefs.js +28 -0
  45. package/admin/src/content-manager/utils/getMaxTempKey.js +1 -1
  46. package/admin/src/content-manager/utils/index.js +7 -0
  47. package/admin/src/core/utils/axiosInstance.js +4 -2
  48. package/admin/src/hooks/index.js +1 -0
  49. package/admin/src/hooks/useFetchClient/index.js +23 -0
  50. package/admin/src/hooks/useSettingsMenu/init.js +0 -7
  51. package/admin/src/pages/Admin/Onboarding/index.js +42 -44
  52. package/admin/src/pages/App/index.js +20 -13
  53. package/admin/src/pages/AuthPage/components/Register/index.js +1 -1
  54. package/admin/src/pages/AuthPage/components/ResetPassword/index.js +1 -1
  55. package/admin/src/pages/HomePage/SocialLinks.js +4 -4
  56. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/index.js +2 -3
  57. package/admin/src/pages/SettingsPage/pages/Users/ListPage/ModalForm/index.js +23 -18
  58. package/admin/src/pages/SettingsPage/utils/defaultRoutes.js +0 -11
  59. package/admin/src/permissions/defaultPermissions.js +0 -4
  60. package/admin/src/translations/en.json +8 -8
  61. package/admin/src/translations/sk.json +274 -52
  62. package/admin/src/translations/tr.json +485 -5
  63. package/admin/src/utils/fetchClient.js +45 -0
  64. package/admin/src/utils/getFetchClient.js +10 -0
  65. package/admin/src/utils/index.js +1 -0
  66. package/admin/src/utils/uniqueAdminHash.js +22 -0
  67. package/build/2235.06c13219.chunk.js +106 -0
  68. package/build/2598.962797b2.chunk.js +159 -0
  69. package/build/4318.0bbd3f4b.chunk.js +30 -0
  70. package/build/4958.7c118f5e.chunk.js +276 -0
  71. package/build/5052.712419ea.chunk.js +65 -0
  72. package/build/7295.04ac49dc.chunk.js +114 -0
  73. package/build/805.ddcead70.chunk.js +138 -0
  74. package/build/{8633.8da5488a.chunk.js → 8633.59223842.chunk.js} +1 -1
  75. package/build/874.bde3ea04.chunk.js +104 -0
  76. package/build/{1233.80b05d66.chunk.js → 9159.ac968e72.chunk.js} +67 -67
  77. package/build/9707.77e475ee.chunk.js +101 -0
  78. package/build/Admin-authenticatedApp.9dd415b8.chunk.js +72 -0
  79. package/build/{Admin_homePage.54e33c2d.chunk.js → Admin_homePage.8945f71a.chunk.js} +5 -5
  80. package/build/{Admin_marketplace.8219fda6.chunk.js → Admin_marketplace.ed754a4a.chunk.js} +1 -1
  81. package/build/Admin_pluginsPage.67728975.chunk.js +6 -0
  82. package/build/{Admin_profilePage.e9fcce92.chunk.js → Admin_profilePage.60ab80bb.chunk.js} +1 -1
  83. package/build/{Admin_settingsPage.d3f48e9e.chunk.js → Admin_settingsPage.9ce40fed.chunk.js} +15 -15
  84. package/build/Upload_ConfigureTheView.7cb2a3fd.chunk.js +1 -0
  85. package/build/admin-app.d8fc7c4d.chunk.js +112 -0
  86. package/build/admin-edit-roles-page.f407538c.chunk.js +1 -0
  87. package/build/admin-edit-users.5547b126.chunk.js +10 -0
  88. package/build/{admin-users.a0748674.chunk.js → admin-users.4b6b47f8.chunk.js} +2 -2
  89. package/build/api-tokens-create-page.dd4ddfcb.chunk.js +1 -0
  90. package/build/api-tokens-edit-page.821c5a6c.chunk.js +1 -0
  91. package/build/{api-tokens-list-page.700e575f.chunk.js → api-tokens-list-page.50519ed7.chunk.js} +1 -1
  92. package/build/content-manager.f2214e32.chunk.js +1166 -0
  93. package/build/content-type-builder-list-view.4aea46fa.chunk.js +198 -0
  94. package/build/content-type-builder-translation-de-json.a52482c7.chunk.js +1 -0
  95. package/build/content-type-builder-translation-dk-json.a8616510.chunk.js +1 -0
  96. package/build/content-type-builder-translation-en-json.1d9a3c14.chunk.js +1 -0
  97. package/build/content-type-builder-translation-es-json.c3ea46fb.chunk.js +1 -0
  98. package/build/content-type-builder-translation-ko-json.3fb7ddc8.chunk.js +1 -0
  99. package/build/content-type-builder-translation-pl-json.9b2993b2.chunk.js +1 -0
  100. package/build/content-type-builder-translation-pt-BR-json.6d255441.chunk.js +1 -0
  101. package/build/content-type-builder-translation-sv-json.c608b9ca.chunk.js +1 -0
  102. package/build/content-type-builder-translation-tr-json.949e22eb.chunk.js +1 -0
  103. package/build/content-type-builder-translation-zh-json.b79513e4.chunk.js +1 -0
  104. package/build/content-type-builder.8a9a77f9.chunk.js +127 -0
  105. package/build/email-settings-page.c6e62f6b.chunk.js +15 -0
  106. package/build/email-translation-tr-json.8aa034bb.chunk.js +1 -0
  107. package/build/en-json.1abdade9.chunk.js +1 -0
  108. package/build/{i18n-settings-page.195d42fe.chunk.js → i18n-settings-page.ee572037.chunk.js} +1 -1
  109. package/build/i18n-translation-tr-json.34ca9d61.chunk.js +1 -0
  110. package/build/index.html +1 -1
  111. package/build/main.91f6e21e.js +4099 -0
  112. package/build/runtime~main.447b0382.js +2 -0
  113. package/build/sk-json.2af48064.chunk.js +1 -0
  114. package/build/sso-settings-page.91924df1.chunk.js +41 -0
  115. package/build/tr-json.eac8bd79.chunk.js +1 -0
  116. package/build/upload-settings.326cd9fd.chunk.js +89 -0
  117. package/build/upload-translation-en-json.32cf9aff.chunk.js +1 -0
  118. package/build/upload-translation-sk-json.fe86c53b.chunk.js +1 -0
  119. package/build/upload-translation-tr-json.b173223a.chunk.js +1 -0
  120. package/build/upload.2977cb13.chunk.js +38 -0
  121. package/build/users-advanced-settings-page.0c0b8230.chunk.js +13 -0
  122. package/build/users-email-settings-page.18d4a475.chunk.js +28 -0
  123. package/build/{users-permissions-translation-dk-json.fe39c74b.chunk.js → users-permissions-translation-dk-json.bad0b786.chunk.js} +1 -1
  124. package/build/{users-permissions-translation-en-json.765abf48.chunk.js → users-permissions-translation-en-json.aeab388a.chunk.js} +1 -1
  125. package/build/{users-permissions-translation-es-json.1bb9cde2.chunk.js → users-permissions-translation-es-json.152a923f.chunk.js} +1 -1
  126. package/build/{users-permissions-translation-ko-json.3be77775.chunk.js → users-permissions-translation-ko-json.6bd0ae22.chunk.js} +1 -1
  127. package/build/{users-permissions-translation-pl-json.1dbdd4a1.chunk.js → users-permissions-translation-pl-json.c6a02992.chunk.js} +1 -1
  128. package/build/{users-permissions-translation-sv-json.d5d11648.chunk.js → users-permissions-translation-sv-json.370d6eee.chunk.js} +1 -1
  129. package/build/users-permissions-translation-tr-json.9bebc250.chunk.js +1 -0
  130. package/build/{users-permissions-translation-zh-json.92f406f9.chunk.js → users-permissions-translation-zh-json.1fea833f.chunk.js} +1 -1
  131. package/build/users-providers-settings-page.25dd858e.chunk.js +1 -0
  132. package/build/users-roles-settings-page.8482a999.chunk.js +30 -0
  133. package/build/{webhook-edit-page.14ad1e6e.chunk.js → webhook-edit-page.dcc3d145.chunk.js} +4 -4
  134. package/build/{webhook-list-page.b87821f2.chunk.js → webhook-list-page.894e6959.chunk.js} +1 -1
  135. package/ee/server/services/passport/provider-registry.js +1 -1
  136. package/package.json +16 -22
  137. package/server/controllers/admin.js +2 -0
  138. package/server/routes/admin.js +1 -1
  139. package/server/services/metrics.js +5 -2
  140. package/server/services/role.js +1 -0
  141. package/utils/get-plugins-path.js +17 -3
  142. package/webpack.alias.js +0 -2
  143. package/admin/src/content-manager/components/BackHeader/index.js +0 -8
  144. package/admin/src/content-manager/components/Block/components.js +0 -28
  145. package/admin/src/content-manager/components/Block/index.js +0 -43
  146. package/admin/src/content-manager/components/Container/index.js +0 -7
  147. package/admin/src/content-manager/components/CustomInputCheckbox/components.js +0 -77
  148. package/admin/src/content-manager/components/CustomInputCheckbox/index.js +0 -53
  149. package/admin/src/content-manager/components/DynamicComponentCard/Wrapper.js +0 -63
  150. package/admin/src/content-manager/components/FilterOptionsCTA/index.js +0 -14
  151. package/admin/src/content-manager/components/FormTitle/index.js +0 -22
  152. package/admin/src/content-manager/components/FormWrapper/index.js +0 -20
  153. package/admin/src/content-manager/components/InputJSON/FieldError.js +0 -38
  154. package/admin/src/content-manager/components/LayoutTitle/index.js +0 -19
  155. package/admin/src/content-manager/components/PlusButton/index.js +0 -52
  156. package/admin/src/content-manager/components/PreviewCarret/components.js +0 -27
  157. package/admin/src/content-manager/components/PreviewCarret/index.js +0 -22
  158. package/admin/src/content-manager/components/RepeatableComponent/AccordionGroupCustom/index.js +0 -122
  159. package/admin/src/content-manager/components/RepeatableComponent/AddFieldButton.js +0 -58
  160. package/admin/src/content-manager/components/RepeatableComponent/DraggedItem/DraggingSibling.js +0 -72
  161. package/admin/src/content-manager/components/RepeatableComponent/DraggedItem/IconButtonCustoms.js +0 -32
  162. package/admin/src/content-manager/components/RepeatableComponent/DraggedItem/index.js +0 -322
  163. package/admin/src/content-manager/components/RepeatableComponent/DraggedItem/utils/connect.js +0 -11
  164. package/admin/src/content-manager/components/RepeatableComponent/DraggedItem/utils/index.js +0 -2
  165. package/admin/src/content-manager/components/RepeatableComponent/DraggedItem/utils/select.js +0 -30
  166. package/admin/src/content-manager/components/RepeatableComponent/utils/connect.js +0 -11
  167. package/admin/src/content-manager/components/RepeatableComponent/utils/select.js +0 -12
  168. package/admin/src/content-manager/components/SectionTitle/Title.js +0 -11
  169. package/admin/src/content-manager/components/SectionTitle/index.js +0 -26
  170. package/admin/src/content-manager/hooks/__test__/usePrev.test.js +0 -26
  171. package/admin/src/pages/SettingsPage/pages/AuditLogs/ListView/DynamicTable/TableRows/CellValue.js +0 -19
  172. package/admin/src/pages/SettingsPage/pages/AuditLogs/ListView/DynamicTable/TableRows/index.js +0 -65
  173. package/admin/src/pages/SettingsPage/pages/AuditLogs/ListView/ModalDialog/ActionItem.js +0 -25
  174. package/admin/src/pages/SettingsPage/pages/AuditLogs/ListView/ModalDialog/index.js +0 -76
  175. package/admin/src/pages/SettingsPage/pages/AuditLogs/ListView/hooks/useFormatTimeStamp.js +0 -24
  176. package/admin/src/pages/SettingsPage/pages/AuditLogs/ListView/index.js +0 -84
  177. package/admin/src/pages/SettingsPage/pages/AuditLogs/ListView/utils/tableHeaders.js +0 -37
  178. package/admin/src/pages/SettingsPage/pages/AuditLogs/ProtectedListPage/index.js +0 -12
  179. package/build/1551f4f60c37af51121f.woff2 +0 -0
  180. package/build/1920.74a262e7.chunk.js +0 -245
  181. package/build/1e59d2330b4c6deb84b3.ttf +0 -0
  182. package/build/20fd1704ea223900efa9.woff2 +0 -0
  183. package/build/2285773e6b4b172f07d9.woff +0 -0
  184. package/build/23f19bb08961f37aaf69.eot +0 -0
  185. package/build/2438.61291207.chunk.js +0 -2183
  186. package/build/2517.9b4940f3.chunk.js +0 -117
  187. package/build/2f517e09eb2ca6650ff5.svg +0 -3717
  188. package/build/4306.f03c2b46.chunk.js +0 -98
  189. package/build/4318.7931eee7.chunk.js +0 -30
  190. package/build/4689f52cc96215721344.svg +0 -801
  191. package/build/491974d108fe4002b2aa.ttf +0 -0
  192. package/build/4986.3820d11d.chunk.js +0 -145
  193. package/build/504.9aeff724.chunk.js +0 -758
  194. package/build/527940b104eb2ea366c8.ttf +0 -0
  195. package/build/77206a6bb316fa0aded5.eot +0 -0
  196. package/build/7a3337626410ca2f4071.woff2 +0 -0
  197. package/build/7a8b4f130182d19a2d7c.svg +0 -5034
  198. package/build/805.e991a370.chunk.js +0 -138
  199. package/build/8b43027f47b20503057d.eot +0 -0
  200. package/build/9707.a0cc4ad8.chunk.js +0 -70
  201. package/build/9bbb245e67a133f6e486.eot +0 -0
  202. package/build/Admin-authenticatedApp.ac85652e.chunk.js +0 -80
  203. package/build/Admin_pluginsPage.3c872de7.chunk.js +0 -6
  204. package/build/admin-app.77179e07.chunk.js +0 -112
  205. package/build/admin-audit-logs.334ee871.chunk.js +0 -1
  206. package/build/admin-edit-roles-page.23f15909.chunk.js +0 -1
  207. package/build/admin-edit-users.283b49ed.chunk.js +0 -10
  208. package/build/api-tokens-create-page.93dd0689.chunk.js +0 -1
  209. package/build/api-tokens-edit-page.b0adac81.chunk.js +0 -1
  210. package/build/bb58e57c48a3e911f15f.woff +0 -0
  211. package/build/be9ee23c0c6390141475.ttf +0 -0
  212. package/build/c1e38fd9e0e74ba58f7a.svg +0 -2671
  213. package/build/content-manager.01e04e11.chunk.js +0 -1200
  214. package/build/content-type-builder-list-view.4412efc3.chunk.js +0 -201
  215. package/build/content-type-builder-translation-de-json.0d7696b9.chunk.js +0 -1
  216. package/build/content-type-builder-translation-dk-json.4729f055.chunk.js +0 -1
  217. package/build/content-type-builder-translation-en-json.f985c9c4.chunk.js +0 -1
  218. package/build/content-type-builder-translation-es-json.333cf47f.chunk.js +0 -1
  219. package/build/content-type-builder-translation-ko-json.51201b12.chunk.js +0 -1
  220. package/build/content-type-builder-translation-pl-json.4a42349b.chunk.js +0 -1
  221. package/build/content-type-builder-translation-pt-BR-json.6fe3b8d1.chunk.js +0 -1
  222. package/build/content-type-builder-translation-sv-json.6deff030.chunk.js +0 -1
  223. package/build/content-type-builder-translation-tr-json.2e52bc60.chunk.js +0 -1
  224. package/build/content-type-builder-translation-zh-json.3b0afd31.chunk.js +0 -1
  225. package/build/content-type-builder.aa4ec633.chunk.js +0 -145
  226. package/build/d878b0a6a1144760244f.woff2 +0 -0
  227. package/build/eeccf4f66002c6f2ba24.woff +0 -0
  228. package/build/email-settings-page.d44a57cb.chunk.js +0 -15
  229. package/build/email-translation-tr-json.87f2feb3.chunk.js +0 -1
  230. package/build/en-json.57917cb1.chunk.js +0 -1
  231. package/build/f691f37e57f04c152e23.woff +0 -0
  232. package/build/fontawesome-css-all.15068c6e.chunk.js +0 -4618
  233. package/build/fontawesome-css.418f40da.chunk.js +0 -6
  234. package/build/fontawesome-js.252cc5f3.chunk.js +0 -7
  235. package/build/main.f31112a5.js +0 -2034
  236. package/build/runtime~main.81f05721.js +0 -2
  237. package/build/sk-json.7ba4b330.chunk.js +0 -1
  238. package/build/sso-settings-page.9f091262.chunk.js +0 -1
  239. package/build/tr-json.9c44ea0c.chunk.js +0 -1
  240. package/build/upload-settings.450cab1a.chunk.js +0 -18
  241. package/build/upload-translation-en-json.86da7b0a.chunk.js +0 -1
  242. package/build/upload-translation-sk-json.b03d4904.chunk.js +0 -1
  243. package/build/upload.a73936d9.chunk.js +0 -64
  244. package/build/users-advanced-settings-page.dc23bc56.chunk.js +0 -13
  245. package/build/users-email-settings-page.6541d372.chunk.js +0 -28
  246. package/build/users-permissions-translation-tr-json.cdc49a3c.chunk.js +0 -1
  247. package/build/users-providers-settings-page.e11a2f64.chunk.js +0 -33
  248. package/build/users-roles-settings-page.445e5e16.chunk.js +0 -30
@@ -2,9 +2,9 @@ import React, { memo, useMemo, useState } from 'react';
2
2
  import get from 'lodash/get';
3
3
  import isEqual from 'react-fast-compare';
4
4
  import PropTypes from 'prop-types';
5
- import { Stack } from '@strapi/design-system/Stack';
6
- import { Box } from '@strapi/design-system/Box';
5
+ import { Box, Stack, VisuallyHidden } from '@strapi/design-system';
7
6
  import { NotAllowedInput, useNotification } from '@strapi/helper-plugin';
7
+ import { useIntl } from 'react-intl';
8
8
 
9
9
  import { getTrad } from '../../utils';
10
10
 
@@ -27,14 +27,16 @@ const DynamicZone = ({
27
27
  isFieldAllowed,
28
28
  isFieldReadable,
29
29
  labelAction,
30
- moveComponentUp,
31
- moveComponentDown,
30
+ moveComponentField,
32
31
  removeComponentFromDynamicZone,
33
32
  dynamicDisplayedComponents,
34
33
  fieldSchema,
35
34
  metadatas,
36
35
  }) => {
37
36
  const [addComponentIsOpen, setAddComponentIsOpen] = useState(false);
37
+ const [liveText, setLiveText] = useState('');
38
+
39
+ const { formatMessage } = useIntl();
38
40
 
39
41
  const toggleNotification = useNotification();
40
42
  const { getComponentLayout, components } = useContentTypeLayout();
@@ -82,12 +84,76 @@ const DynamicZone = ({
82
84
  }
83
85
  };
84
86
 
85
- const handleMoveComponentDown = (name, componentIndex) => () => {
86
- moveComponentDown(name, componentIndex);
87
+ const handleMoveComponent = (newIndex, currentIndex) => {
88
+ setLiveText(
89
+ formatMessage(
90
+ {
91
+ id: getTrad('dnd.reorder'),
92
+ defaultMessage: '{item}, moved. New position in list: {position}.',
93
+ },
94
+ {
95
+ item: `${name}.${currentIndex}`,
96
+ position: getItemPos(newIndex),
97
+ }
98
+ )
99
+ );
100
+
101
+ moveComponentField({
102
+ name,
103
+ newIndex,
104
+ currentIndex,
105
+ });
87
106
  };
88
107
 
89
- const handleMoveComponentUp = (name, componentIndex) => () => {
90
- moveComponentUp(name, componentIndex);
108
+ /**
109
+ *
110
+ * @param {number} index
111
+ * @returns {string}
112
+ */
113
+ const getItemPos = (index) => `${index + 1} of ${dynamicDisplayedComponents.length}`;
114
+
115
+ const handleCancel = (index) => {
116
+ setLiveText(
117
+ formatMessage(
118
+ {
119
+ id: getTrad('dnd.cancel-item'),
120
+ defaultMessage: '{item}, dropped. Re-order cancelled.',
121
+ },
122
+ {
123
+ item: `${name}.${index}`,
124
+ }
125
+ )
126
+ );
127
+ };
128
+
129
+ const handleGrabItem = (index) => {
130
+ setLiveText(
131
+ formatMessage(
132
+ {
133
+ id: getTrad('dnd.grab-item'),
134
+ defaultMessage: `{item}, grabbed. Current position in list: {position}. Press up and down arrow to change position, Spacebar to drop, Escape to cancel.`,
135
+ },
136
+ {
137
+ item: `${name}.${index}`,
138
+ position: getItemPos(index),
139
+ }
140
+ )
141
+ );
142
+ };
143
+
144
+ const handleDropItem = (index) => {
145
+ setLiveText(
146
+ formatMessage(
147
+ {
148
+ id: getTrad('dnd.drop-item'),
149
+ defaultMessage: `{item}, dropped. Final position in list: {position}.`,
150
+ },
151
+ {
152
+ item: `${name}.${index}`,
153
+ position: getItemPos(index),
154
+ }
155
+ )
156
+ );
91
157
  };
92
158
 
93
159
  const handleRemoveComponent = (name, currentIndex) => () => {
@@ -105,6 +171,8 @@ const DynamicZone = ({
105
171
  );
106
172
  }
107
173
 
174
+ const ariaDescriptionId = `${name}-item-instructions`;
175
+
108
176
  return (
109
177
  <Stack spacing={6}>
110
178
  {dynamicDisplayedComponentsLength > 0 && (
@@ -117,27 +185,30 @@ const DynamicZone = ({
117
185
  numberOfComponents={dynamicDisplayedComponentsLength}
118
186
  required={fieldSchema.required || false}
119
187
  />
120
- {dynamicDisplayedComponents.map((componentUid, index) => {
121
- const showDownIcon = isFieldAllowed && index < dynamicDisplayedComponentsLength - 1;
122
- const showUpIcon = isFieldAllowed && index > 0;
123
-
124
- return (
188
+ <VisuallyHidden id={ariaDescriptionId}>
189
+ {formatMessage({
190
+ id: getTrad('dnd.instructions'),
191
+ defaultMessage: `Press spacebar to grab and re-order`,
192
+ })}
193
+ </VisuallyHidden>
194
+ <VisuallyHidden aria-live="assertive">{liveText}</VisuallyHidden>
195
+ <ol aria-describedby={ariaDescriptionId}>
196
+ {dynamicDisplayedComponents.map(({ componentUid, id }, index) => (
125
197
  <DynamicZoneComponent
126
198
  componentUid={componentUid}
127
199
  formErrors={formErrors}
128
- // eslint-disable-next-line react/no-array-index-key
129
- key={index}
200
+ key={`${componentUid}-${id}`}
130
201
  index={index}
131
202
  isFieldAllowed={isFieldAllowed}
132
- onMoveComponentDownClick={handleMoveComponentDown(name, index)}
133
- onMoveComponentUpClick={handleMoveComponentUp(name, index)}
134
203
  name={name}
204
+ onMoveComponent={handleMoveComponent}
135
205
  onRemoveComponentClick={handleRemoveComponent(name, index)}
136
- showDownIcon={showDownIcon}
137
- showUpIcon={showUpIcon}
206
+ onCancel={handleCancel}
207
+ onDropItem={handleDropItem}
208
+ onGrabItem={handleGrabItem}
138
209
  />
139
- );
140
- })}
210
+ ))}
211
+ </ol>
141
212
  </Box>
142
213
  )}
143
214
 
@@ -172,7 +243,12 @@ DynamicZone.defaultProps = {
172
243
 
173
244
  DynamicZone.propTypes = {
174
245
  addComponentToDynamicZone: PropTypes.func.isRequired,
175
- dynamicDisplayedComponents: PropTypes.array,
246
+ dynamicDisplayedComponents: PropTypes.arrayOf(
247
+ PropTypes.shape({
248
+ componentUid: PropTypes.string.isRequired,
249
+ id: PropTypes.number.isRequired,
250
+ })
251
+ ),
176
252
  fieldSchema: PropTypes.shape({
177
253
  components: PropTypes.array.isRequired,
178
254
  max: PropTypes.number,
@@ -188,8 +264,7 @@ DynamicZone.propTypes = {
188
264
  description: PropTypes.string,
189
265
  label: PropTypes.string,
190
266
  }).isRequired,
191
- moveComponentUp: PropTypes.func.isRequired,
192
- moveComponentDown: PropTypes.func.isRequired,
267
+ moveComponentField: PropTypes.func.isRequired,
193
268
  name: PropTypes.string.isRequired,
194
269
  removeComponentFromDynamicZone: PropTypes.func.isRequired,
195
270
  };
@@ -9,15 +9,20 @@ function useSelect(name) {
9
9
  isCreatingEntry,
10
10
  formErrors,
11
11
  modifiedData,
12
- moveComponentUp,
13
- moveComponentDown,
12
+ moveComponentField,
14
13
  removeComponentFromDynamicZone,
15
14
  readActionAllowedFields,
16
15
  updateActionAllowedFields,
17
16
  } = useCMEditViewDataManager();
18
17
 
19
18
  const dynamicDisplayedComponents = useMemo(
20
- () => get(modifiedData, [name], []).map((data) => data.__component),
19
+ () =>
20
+ get(modifiedData, [name], []).map((data) => {
21
+ return {
22
+ componentUid: data.__component,
23
+ id: data.id ?? data.__temp_key__,
24
+ };
25
+ }),
21
26
  [modifiedData, name]
22
27
  );
23
28
 
@@ -39,8 +44,7 @@ function useSelect(name) {
39
44
  isCreatingEntry,
40
45
  isFieldAllowed,
41
46
  isFieldReadable,
42
- moveComponentUp,
43
- moveComponentDown,
47
+ moveComponentField,
44
48
  removeComponentFromDynamicZone,
45
49
  dynamicDisplayedComponents,
46
50
  };
@@ -8,7 +8,9 @@ import set from 'lodash/set';
8
8
  import PropTypes from 'prop-types';
9
9
  import { useIntl } from 'react-intl';
10
10
  import { Prompt, Redirect } from 'react-router-dom';
11
- import { Main } from '@strapi/design-system/Main';
11
+ import { useDispatch, useSelector } from 'react-redux';
12
+
13
+ import { Main } from '@strapi/design-system';
12
14
  import {
13
15
  LoadingIndicatorPage,
14
16
  ContentManagerEditViewDataManagerContext,
@@ -20,8 +22,13 @@ import {
20
22
  } from '@strapi/helper-plugin';
21
23
 
22
24
  import { getTrad, removeKeyInObject } from '../../utils';
25
+
26
+ import selectCrudReducer from '../../sharedReducers/crudReducer/selectors';
27
+
23
28
  import reducer, { initialState } from './reducer';
24
29
  import { cleanData, createYupSchema, recursivelyFindPathsBasedOnCondition } from './utils';
30
+ import { clearSetModifiedDataOnly } from '../../sharedReducers/crudReducer/actions';
31
+ import { usePrev } from '../../hooks';
25
32
 
26
33
  const EditViewDataManagerProvider = ({
27
34
  allLayoutData,
@@ -61,6 +68,9 @@ const EditViewDataManagerProvider = ({
61
68
  publishConfirmation,
62
69
  } = reducerState;
63
70
 
71
+ const { setModifiedDataOnly } = useSelector(selectCrudReducer);
72
+ const reduxDispatch = useDispatch();
73
+
64
74
  const toggleNotification = useNotification();
65
75
  const { lockApp, unlockApp } = useOverlayBlocker();
66
76
 
@@ -144,8 +154,18 @@ const EditViewDataManagerProvider = ({
144
154
 
145
155
  const { components } = allLayoutData;
146
156
 
157
+ const previousInitialValues = usePrev(initialValues);
158
+
147
159
  useEffect(() => {
148
- if (initialValues && currentContentTypeLayout?.attributes) {
160
+ /**
161
+ * Only fire this effect if the initialValues are different
162
+ * otherwise it's a fruitless effort no matter what happens.
163
+ */
164
+ if (
165
+ initialValues &&
166
+ currentContentTypeLayout?.attributes &&
167
+ !isEqual(previousInitialValues, initialValues)
168
+ ) {
149
169
  /**
150
170
  * This will return an array of paths:
151
171
  * ['many_to_one', 'one_to_many', 'one_to_one']
@@ -179,9 +199,25 @@ const EditViewDataManagerProvider = ({
179
199
  componentPaths,
180
200
  repeatableComponentPaths,
181
201
  dynamicZonePaths,
202
+ setModifiedDataOnly,
182
203
  });
204
+
205
+ /**
206
+ * TODO: This should be moved to a side-effect e.g. thunks
207
+ * something to consider for V5
208
+ */
209
+ if (setModifiedDataOnly) {
210
+ reduxDispatch(clearSetModifiedDataOnly());
211
+ }
183
212
  }
184
- }, [initialValues, currentContentTypeLayout, components]);
213
+ }, [
214
+ initialValues,
215
+ currentContentTypeLayout,
216
+ components,
217
+ setModifiedDataOnly,
218
+ reduxDispatch,
219
+ previousInitialValues,
220
+ ]);
185
221
 
186
222
  const dispatchAddComponent = useCallback(
187
223
  (type) =>
@@ -216,7 +252,7 @@ const EditViewDataManagerProvider = ({
216
252
  /**
217
253
  * @type {({ name: string, value: Relation, toOneRelation: boolean}) => void}
218
254
  */
219
- const connectRelation = useCallback(({ name, value, toOneRelation }) => {
255
+ const relationConnect = useCallback(({ name, value, toOneRelation }) => {
220
256
  dispatch({
221
257
  type: 'CONNECT_RELATION',
222
258
  keys: name.split('.'),
@@ -225,7 +261,7 @@ const EditViewDataManagerProvider = ({
225
261
  });
226
262
  }, []);
227
263
 
228
- const loadRelation = useCallback(({ target: { name, value } }) => {
264
+ const relationLoad = useCallback(({ target: { name, value } }) => {
229
265
  dispatch({
230
266
  type: 'LOAD_RELATION',
231
267
  keys: name.split('.'),
@@ -498,16 +534,16 @@ const EditViewDataManagerProvider = ({
498
534
  [shouldCheckDZErrors]
499
535
  );
500
536
 
501
- const moveComponentField = useCallback((pathToComponent, dragIndex, hoverIndex) => {
537
+ const moveComponentField = useCallback(({ name, newIndex, currentIndex }) => {
502
538
  dispatch({
503
539
  type: 'MOVE_COMPONENT_FIELD',
504
- pathToComponent,
505
- dragIndex,
506
- hoverIndex,
540
+ keys: name.split('.'),
541
+ newIndex,
542
+ oldIndex: currentIndex,
507
543
  });
508
544
  }, []);
509
545
 
510
- const disconnectRelation = useCallback(({ name, id }) => {
546
+ const relationDisconnect = useCallback(({ name, id }) => {
511
547
  dispatch({
512
548
  type: 'DISCONNECT_RELATION',
513
549
  keys: name.split('.'),
@@ -515,6 +551,25 @@ const EditViewDataManagerProvider = ({
515
551
  });
516
552
  }, []);
517
553
 
554
+ /**
555
+ * @typedef Payload
556
+ * @type {object}
557
+ * @property {string} name - The name of the field in `modifiedData`
558
+ * @property {number} oldIndex - The relation's current index
559
+ * @property {number} newIndex - The relation's new index
560
+ *
561
+ *
562
+ * @type {(payload: Payload) => void}
563
+ */
564
+ const relationReorder = useCallback(({ name, oldIndex, newIndex }) => {
565
+ dispatch({
566
+ type: 'REORDER_RELATION',
567
+ keys: name.split('.'),
568
+ oldIndex,
569
+ newIndex,
570
+ });
571
+ }, []);
572
+
518
573
  const removeComponentFromDynamicZone = useCallback(
519
574
  (dynamicZoneName, index) => {
520
575
  trackUsageRef.current('removeComponentFromDynamicZone');
@@ -565,7 +620,6 @@ const EditViewDataManagerProvider = ({
565
620
  value={{
566
621
  addComponentToDynamicZone,
567
622
  addNonRepeatableComponentToField,
568
- connectRelation,
569
623
  addRepeatableComponentToField,
570
624
  allLayoutData,
571
625
  checkFormErrors,
@@ -578,20 +632,28 @@ const EditViewDataManagerProvider = ({
578
632
  shouldNotRunValidations,
579
633
  status,
580
634
  layout: currentContentTypeLayout,
581
- loadRelation,
582
635
  modifiedData,
583
- moveComponentDown,
584
636
  moveComponentField,
637
+ /**
638
+ * @deprecated use `moveComponentField` instead. This will be removed in v5.
639
+ */
640
+ moveComponentDown,
641
+ /**
642
+ * @deprecated use `moveComponentField` instead. This will be removed in v5.
643
+ */
585
644
  moveComponentUp,
586
645
  onChange: handleChange,
587
646
  onPublish: handlePublish,
588
647
  onUnpublish,
589
- disconnectRelation,
590
648
  readActionAllowedFields,
591
649
  redirectToPreviousPage,
592
650
  removeComponentFromDynamicZone,
593
651
  removeComponentFromField,
594
652
  removeRepeatableField,
653
+ relationConnect,
654
+ relationDisconnect,
655
+ relationLoad,
656
+ relationReorder,
595
657
  slug,
596
658
  triggerFormValidation,
597
659
  updateActionAllowedFields,
@@ -88,6 +88,7 @@ const reducer = (state, action) =>
88
88
  ? {
89
89
  ...state.componentsDataStructure[componentLayoutData.uid],
90
90
  __component: componentLayoutData.uid,
91
+ __temp_key__: getMaxTempKey(currentValue) + 1,
91
92
  }
92
93
  : {
93
94
  ...state.componentsDataStructure[componentLayoutData.uid],
@@ -104,6 +105,11 @@ const reducer = (state, action) =>
104
105
  (value) => value.type === 'component' && value.repeatable
105
106
  )(componentLayoutData.attributes);
106
107
 
108
+ const nonRepeatableComponentPaths = recursivelyFindPathsBasedOnCondition(
109
+ allComponents,
110
+ (value) => value.type === 'component' && !value.repeatable
111
+ )(componentLayoutData.attributes);
112
+
107
113
  const componentDataStructure = relationPaths.reduce((acc, current) => {
108
114
  const [componentName] = current.split('.');
109
115
 
@@ -112,8 +118,21 @@ const reducer = (state, action) =>
112
118
  * has another repeatable component inside of it we
113
119
  * don't need to attach the array at this point because that will be
114
120
  * done again deeper in the nest.
121
+ *
122
+ * We also need to handle cases with single components nested within
123
+ * repeatables by checking that the relation path does not match a
124
+ * non-repeatable component path. This accounts for component
125
+ * structures such as:
126
+ * - outer_single_compo
127
+ * - level_one_repeatable
128
+ * - level_two_single_component
129
+ * - level_three_repeatable
115
130
  */
116
- if (!repeatableFields.includes(componentName)) {
131
+
132
+ if (
133
+ !repeatableFields.includes(componentName) &&
134
+ !nonRepeatableComponentPaths.includes(componentName)
135
+ ) {
117
136
  set(acc, current, []);
118
137
  }
119
138
 
@@ -128,7 +147,6 @@ const reducer = (state, action) =>
128
147
 
129
148
  break;
130
149
  }
131
-
132
150
  case 'LOAD_RELATION': {
133
151
  const initialDataPath = ['initialData', ...action.keys];
134
152
  const modifiedDataPath = ['modifiedData', ...action.keys];
@@ -172,15 +190,29 @@ const reducer = (state, action) =>
172
190
  const { id } = action;
173
191
  const modifiedDataRelation = get(state, [...path]);
174
192
 
175
- /**
176
- * TODO: before merge make this performant (e.g. 1000 relations === long time)
177
- */
178
193
  const newRelations = modifiedDataRelation.filter((rel) => rel.id !== id);
179
194
 
180
195
  set(draftState, path, newRelations);
181
196
 
182
197
  break;
183
198
  }
199
+ case 'MOVE_COMPONENT_FIELD':
200
+ case 'REORDER_RELATION': {
201
+ const { oldIndex, newIndex, keys } = action;
202
+ const path = ['modifiedData', ...keys];
203
+ const modifiedDataRelations = get(state, [...path]);
204
+
205
+ const currentItem = modifiedDataRelations[oldIndex];
206
+
207
+ const newRelations = [...modifiedDataRelations];
208
+
209
+ newRelations.splice(oldIndex, 1);
210
+ newRelations.splice(newIndex, 0, currentItem);
211
+
212
+ set(draftState, path, newRelations);
213
+
214
+ break;
215
+ }
184
216
  /**
185
217
  * This action will be called when you open your entry (first load)
186
218
  * but also every time you press publish.
@@ -192,6 +224,7 @@ const reducer = (state, action) =>
192
224
  componentPaths = [],
193
225
  repeatableComponentPaths = [],
194
226
  dynamicZonePaths = [],
227
+ setModifiedDataOnly,
195
228
  } = action;
196
229
 
197
230
  /**
@@ -243,7 +276,10 @@ const reducer = (state, action) =>
243
276
  return acc;
244
277
  }, data);
245
278
 
246
- draftState.initialData = mergeDataWithPreparedRelations;
279
+ if (!setModifiedDataOnly) {
280
+ draftState.initialData = mergeDataWithPreparedRelations;
281
+ }
282
+
247
283
  draftState.modifiedData = mergeDataWithPreparedRelations;
248
284
 
249
285
  draftState.formErrors = {};
@@ -252,25 +288,6 @@ const reducer = (state, action) =>
252
288
  draftState.shouldCheckErrors = false;
253
289
  break;
254
290
  }
255
- case 'MOVE_COMPONENT_FIELD': {
256
- const currentValue = get(state, ['modifiedData', ...action.pathToComponent]);
257
- const valueToInsert = get(state, [
258
- 'modifiedData',
259
- ...action.pathToComponent,
260
- action.dragIndex,
261
- ]);
262
-
263
- const updatedValue = moveFields(
264
- currentValue,
265
- action.dragIndex,
266
- action.hoverIndex,
267
- valueToInsert
268
- );
269
-
270
- set(draftState, ['modifiedData', ...action.pathToComponent], updatedValue);
271
-
272
- break;
273
- }
274
291
  case 'MOVE_COMPONENT_UP':
275
292
  case 'MOVE_COMPONENT_DOWN': {
276
293
  const { currentIndex, dynamicZoneName, shouldCheckErrors } = action;
@@ -84,14 +84,28 @@ const cleanData = ({ browserState, serverState }, currentSchema, componentsSchem
84
84
  */
85
85
  let actualOldValue = oldValue ?? [];
86
86
 
87
+ const valuesWithPositions = value.map((relation, index, allRelations) => {
88
+ const nextRelation = allRelations[index + 1];
89
+
90
+ if (nextRelation) {
91
+ return { ...relation, position: { before: nextRelation.id } };
92
+ }
93
+
94
+ return { ...relation, position: { end: true } };
95
+ });
96
+
87
97
  /**
88
98
  * Instead of the full relation object, we only want to send its ID
89
99
  * connectedRelations are the items that are in the browserState
90
100
  * array but not in the serverState
91
101
  */
92
- const connectedRelations = value.reduce((acc, relation) => {
93
- if (!actualOldValue.find((oldRelation) => oldRelation.id === relation.id)) {
94
- return [...acc, { id: relation.id }];
102
+ const connectedRelations = valuesWithPositions.reduce((acc, relation, currentIndex) => {
103
+ const indexOfRelationOnServer = actualOldValue.findIndex(
104
+ (oldRelation) => oldRelation.id === relation.id
105
+ );
106
+
107
+ if (indexOfRelationOnServer === -1 || indexOfRelationOnServer !== currentIndex) {
108
+ return [...acc, { id: relation.id, position: relation.position }];
95
109
  }
96
110
 
97
111
  return acc;
@@ -102,7 +116,7 @@ const cleanData = ({ browserState, serverState }, currentSchema, componentsSchem
102
116
  * are no longer in the browserState
103
117
  */
104
118
  const disconnectedRelations = actualOldValue.reduce((acc, relation) => {
105
- if (!value.find((newRelation) => newRelation.id === relation.id)) {
119
+ if (!valuesWithPositions.find((newRelation) => newRelation.id === relation.id)) {
106
120
  return [...acc, { id: relation.id }];
107
121
  }
108
122
 
@@ -111,7 +125,12 @@ const cleanData = ({ browserState, serverState }, currentSchema, componentsSchem
111
125
 
112
126
  cleanedData = {
113
127
  disconnect: disconnectedRelations,
114
- connect: connectedRelations,
128
+ /**
129
+ * Reverse the array because the API sequentially goes through the list
130
+ * so in an instance where you add two to the end it would fail because index0
131
+ * would want to attach itself to index1 which doesn't exist yet.
132
+ */
133
+ connect: connectedRelations.reverse(),
115
134
  };
116
135
 
117
136
  break;
@@ -55,8 +55,15 @@ const recursivelyFindPathsBasedOnConditionSetup = (components, predicate = () =>
55
55
  *
56
56
  * NOTE: we don't need to know the path to the `array` because it's about data shape not about the actual data
57
57
  */
58
- }).map((path) => path.split(`${componentName}.`)[1]);
58
+ }).map((path) => {
59
+ return path.split(`${componentName}.`)[1];
60
+ });
59
61
  })
62
+ /**
63
+ * We filter because this will give you `dynamiczone.undefined` because the dynamic_zone component
64
+ * is not required to be returned in this circumstance.
65
+ */
66
+ .filter((path) => Boolean(path))
60
67
  .map((path) => `${key}.${path}`);
61
68
 
62
69
  acc = [...acc, attributesInDynamicComponents];
@@ -3,12 +3,18 @@ import PropTypes from 'prop-types';
3
3
  import { useIntl } from 'react-intl';
4
4
  import { Field } from '@strapi/design-system/Field';
5
5
 
6
- const FieldWrapper = ({ name, hint, error, children }) => {
6
+ const FieldWrapper = ({ name, hint, error, children, required }) => {
7
7
  const { formatMessage } = useIntl();
8
8
  const errorMessage = error ? formatMessage({ id: error, defaultMessage: error }) : '';
9
9
 
10
10
  return (
11
- <Field name={name} hint={hint && formatMessage(hint)} error={errorMessage} id={name}>
11
+ <Field
12
+ name={name}
13
+ hint={hint && formatMessage(hint)}
14
+ error={errorMessage}
15
+ id={name}
16
+ required={required}
17
+ >
12
18
  {children}
13
19
  </Field>
14
20
  );
@@ -17,6 +23,7 @@ const FieldWrapper = ({ name, hint, error, children }) => {
17
23
  FieldWrapper.defaultProps = {
18
24
  hint: undefined,
19
25
  error: '',
26
+ required: false,
20
27
  };
21
28
 
22
29
  FieldWrapper.propTypes = {
@@ -27,6 +34,7 @@ FieldWrapper.propTypes = {
27
34
  }),
28
35
  error: PropTypes.string,
29
36
  children: PropTypes.node.isRequired,
37
+ required: PropTypes.bool,
30
38
  };
31
39
 
32
40
  export default FieldWrapper;
@@ -1,18 +1,9 @@
1
1
  import React from 'react';
2
2
  import PropTypes from 'prop-types';
3
- import styled from 'styled-components';
4
3
  import { useIntl } from 'react-intl';
5
4
  import { FieldLabel } from '@strapi/design-system/Field';
6
- import { Box } from '@strapi/design-system/Box';
7
- import { Flex } from '@strapi/design-system/Flex';
8
5
 
9
- const LabelAction = styled(Box)`
10
- svg path {
11
- fill: ${({ theme }) => theme.colors.neutral500};
12
- }
13
- `;
14
-
15
- const Label = ({ intlLabel, labelAction, name, required }) => {
6
+ const Label = ({ intlLabel, labelAction, name }) => {
16
7
  const { formatMessage } = useIntl();
17
8
  const label = intlLabel?.id
18
9
  ? formatMessage(
@@ -21,19 +12,13 @@ const Label = ({ intlLabel, labelAction, name, required }) => {
21
12
  )
22
13
  : name;
23
14
 
24
- return (
25
- <Flex>
26
- <FieldLabel required={required}>{label}</FieldLabel>
27
- {labelAction && <LabelAction paddingLeft={1}>{labelAction}</LabelAction>}
28
- </Flex>
29
- );
15
+ return <FieldLabel action={labelAction}>{label}</FieldLabel>;
30
16
  };
31
17
 
32
18
  Label.defaultProps = {
33
19
  id: undefined,
34
20
  intlLabel: undefined,
35
21
  labelAction: undefined,
36
- required: false,
37
22
  };
38
23
 
39
24
  Label.propTypes = {
@@ -45,7 +30,6 @@ Label.propTypes = {
45
30
  }),
46
31
  labelAction: PropTypes.element,
47
32
  name: PropTypes.string.isRequired,
48
- required: PropTypes.bool,
49
33
  };
50
34
 
51
35
  export default Label;