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

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 (132) hide show
  1. package/admin/src/content-manager/components/ComponentInitializer/index.js +1 -7
  2. package/admin/src/content-manager/components/{RepeatableComponent/DragPreview.js → DragLayer/ComponentDragPreview.js} +25 -12
  3. package/admin/src/content-manager/components/DragLayer/RelationDragPreview.js +75 -0
  4. package/admin/src/content-manager/components/DragLayer/index.js +23 -7
  5. package/admin/src/content-manager/components/DynamicZone/components/DynamicComponent.js +130 -84
  6. package/admin/src/content-manager/components/DynamicZone/index.js +99 -24
  7. package/admin/src/content-manager/components/DynamicZone/utils/select.js +9 -5
  8. package/admin/src/content-manager/components/EditViewDataManagerProvider/index.js +76 -14
  9. package/admin/src/content-manager/components/EditViewDataManagerProvider/reducer.js +23 -23
  10. package/admin/src/content-manager/components/EditViewDataManagerProvider/utils/cleanData.js +24 -5
  11. package/admin/src/content-manager/components/EditViewDataManagerProvider/utils/recursivelyFindPathsBasedOnCondition.js +8 -1
  12. package/admin/src/content-manager/components/Inputs/index.js +5 -19
  13. package/admin/src/content-manager/components/NonRepeatableComponent/index.js +4 -0
  14. package/admin/src/content-manager/components/RelationInput/RelationInput.js +203 -63
  15. package/admin/src/content-manager/components/RelationInput/components/RelationItem.js +134 -21
  16. package/admin/src/content-manager/components/RelationInput/components/RelationList.js +1 -2
  17. package/admin/src/content-manager/components/RelationInput/constants.js +1 -0
  18. package/admin/src/content-manager/components/RelationInputDataManager/RelationInputDataManager.js +132 -10
  19. package/admin/src/content-manager/components/RepeatableComponent/components/Accordion.js +77 -0
  20. package/admin/src/content-manager/components/RepeatableComponent/components/Component.js +262 -0
  21. package/admin/src/content-manager/components/RepeatableComponent/{DraggedItem → components}/Preview.js +0 -0
  22. package/admin/src/content-manager/components/RepeatableComponent/index.js +147 -87
  23. package/admin/src/content-manager/components/RepeatableComponent/utils/getComponentErrorKeys.js +1 -1
  24. package/admin/src/content-manager/components/SingleTypeFormWrapper/index.js +1 -1
  25. package/admin/src/content-manager/components/Wysiwyg/Editor.js +1 -1
  26. package/admin/src/content-manager/hooks/index.js +2 -0
  27. package/admin/src/content-manager/hooks/useDragAndDrop.js +120 -0
  28. package/admin/src/content-manager/hooks/useKeyboardDragAndDrop.js +98 -0
  29. package/admin/src/content-manager/hooks/useLazyComponents/index.js +69 -0
  30. package/admin/src/content-manager/pages/CollectionTypeRecursivePath/components/ErrorFallback.js +13 -0
  31. package/admin/src/content-manager/pages/CollectionTypeRecursivePath/index.js +2 -1
  32. package/admin/src/content-manager/pages/EditView/GridRow/index.js +62 -0
  33. package/admin/src/content-manager/pages/EditView/index.js +74 -154
  34. package/admin/src/content-manager/pages/EditView/selectors.js +14 -0
  35. package/admin/src/content-manager/pages/EditView/utils/createAttributesLayout.js +11 -6
  36. package/admin/src/content-manager/pages/EditView/utils/getCustomFieldUidsFromLayout.js +18 -0
  37. package/admin/src/content-manager/pages/EditView/utils/index.js +1 -0
  38. package/admin/src/content-manager/pages/EditViewLayoutManager/index.js +1 -1
  39. package/admin/src/content-manager/sharedReducers/crudReducer/actions.js +5 -0
  40. package/admin/src/content-manager/sharedReducers/crudReducer/constants.js +2 -0
  41. package/admin/src/content-manager/sharedReducers/crudReducer/reducer.js +7 -0
  42. package/admin/src/content-manager/utils/ItemTypes.js +1 -1
  43. package/admin/src/content-manager/utils/composeRefs.js +28 -0
  44. package/admin/src/content-manager/utils/getMaxTempKey.js +1 -1
  45. package/admin/src/content-manager/utils/index.js +7 -0
  46. package/admin/src/core/utils/axiosInstance.js +4 -2
  47. package/admin/src/hooks/index.js +1 -0
  48. package/admin/src/hooks/useFetchClient/index.js +23 -0
  49. package/admin/src/pages/HomePage/SocialLinks.js +4 -4
  50. package/admin/src/pages/SettingsPage/pages/Users/ListPage/ModalForm/index.js +23 -18
  51. package/admin/src/translations/en.json +6 -0
  52. package/admin/src/translations/ru.json +789 -489
  53. package/admin/src/utils/fetchClient.js +45 -0
  54. package/admin/src/utils/getFetchClient.js +10 -0
  55. package/build/4306.df40a798.chunk.js +98 -0
  56. package/build/{4318.daf31770.chunk.js → 4318.80bdf035.chunk.js} +2 -2
  57. package/build/5057.195a59ff.chunk.js +65 -0
  58. package/build/{805.a1894307.chunk.js → 805.e991a370.chunk.js} +6 -6
  59. package/build/{4986.3820d11d.chunk.js → 8176.b19bc128.chunk.js} +32 -32
  60. package/build/{1233.32d6888d.chunk.js → 8186.55910742.chunk.js} +94 -94
  61. package/build/{8633.8da5488a.chunk.js → 8633.59223842.chunk.js} +1 -1
  62. package/build/8881.c693411a.chunk.js +245 -0
  63. package/build/9161.4a0ab137.chunk.js +2119 -0
  64. package/build/9279.6290c87a.chunk.js +117 -0
  65. package/build/9707.a0cc4ad8.chunk.js +70 -0
  66. package/build/Admin-authenticatedApp.f9e74dc0.chunk.js +80 -0
  67. package/build/{Admin_homePage.b4db4df8.chunk.js → Admin_homePage.8945f71a.chunk.js} +5 -5
  68. package/build/{Admin_marketplace.fa51405b.chunk.js → Admin_marketplace.ed754a4a.chunk.js} +1 -1
  69. package/build/{Admin_pluginsPage.14d2840f.chunk.js → Admin_pluginsPage.3c872de7.chunk.js} +1 -1
  70. package/build/{Admin_profilePage.6c2c8398.chunk.js → Admin_profilePage.c07bdf08.chunk.js} +1 -1
  71. package/build/{Admin_settingsPage.5e740514.chunk.js → Admin_settingsPage.50a8765b.chunk.js} +5 -5
  72. package/build/admin-app.2861b6d2.chunk.js +112 -0
  73. package/build/{admin-edit-roles-page.c7c338b3.chunk.js → admin-edit-roles-page.f407538c.chunk.js} +1 -1
  74. package/build/{admin-edit-users.d254c128.chunk.js → admin-edit-users.85231e4c.chunk.js} +1 -1
  75. package/build/{admin-users.7c423e41.chunk.js → admin-users.a2707644.chunk.js} +2 -2
  76. package/build/api-tokens-create-page.dd4ddfcb.chunk.js +1 -0
  77. package/build/api-tokens-edit-page.821c5a6c.chunk.js +1 -0
  78. package/build/{api-tokens-list-page.fe994b6b.chunk.js → api-tokens-list-page.700e575f.chunk.js} +1 -1
  79. package/build/content-manager.ee948f75.chunk.js +1186 -0
  80. package/build/content-type-builder-list-view.4412efc3.chunk.js +201 -0
  81. package/build/content-type-builder.b132b5f4.chunk.js +145 -0
  82. package/build/email-settings-page.db0d98d1.chunk.js +15 -0
  83. package/build/{en-json.7dd57947.chunk.js → en-json.4a56dca7.chunk.js} +1 -1
  84. package/build/index.html +1 -1
  85. package/build/main.faac89ee.js +2025 -0
  86. package/build/ru-json.8830286f.chunk.js +1 -0
  87. package/build/{runtime~main.6e7d95b9.js → runtime~main.75a15b8e.js} +1 -1
  88. package/build/{sso-settings-page.eb30a02e.chunk.js → sso-settings-page.adb12ac3.chunk.js} +1 -1
  89. package/build/upload-settings.450cab1a.chunk.js +18 -0
  90. package/build/upload.e2034370.chunk.js +64 -0
  91. package/build/users-advanced-settings-page.0c0b8230.chunk.js +13 -0
  92. package/build/users-email-settings-page.3126ff8c.chunk.js +28 -0
  93. package/build/users-providers-settings-page.b7b602e2.chunk.js +33 -0
  94. package/build/users-roles-settings-page.ce5b582d.chunk.js +30 -0
  95. package/build/webhook-edit-page.1215a6b7.chunk.js +75 -0
  96. package/build/{webhook-list-page.42533b59.chunk.js → webhook-list-page.b87821f2.chunk.js} +1 -1
  97. package/ee/server/services/passport/provider-registry.js +1 -1
  98. package/package.json +15 -15
  99. package/utils/get-plugins-path.js +17 -3
  100. package/admin/src/content-manager/components/RepeatableComponent/AccordionGroupCustom/index.js +0 -122
  101. package/admin/src/content-manager/components/RepeatableComponent/AddFieldButton.js +0 -58
  102. package/admin/src/content-manager/components/RepeatableComponent/DraggedItem/DraggingSibling.js +0 -72
  103. package/admin/src/content-manager/components/RepeatableComponent/DraggedItem/IconButtonCustoms.js +0 -32
  104. package/admin/src/content-manager/components/RepeatableComponent/DraggedItem/index.js +0 -322
  105. package/admin/src/content-manager/components/RepeatableComponent/DraggedItem/utils/connect.js +0 -11
  106. package/admin/src/content-manager/components/RepeatableComponent/DraggedItem/utils/index.js +0 -2
  107. package/admin/src/content-manager/components/RepeatableComponent/DraggedItem/utils/select.js +0 -30
  108. package/admin/src/content-manager/components/RepeatableComponent/utils/connect.js +0 -11
  109. package/admin/src/content-manager/components/RepeatableComponent/utils/select.js +0 -12
  110. package/admin/src/content-manager/hooks/__test__/usePrev.test.js +0 -26
  111. package/build/2438.afe24949.chunk.js +0 -2525
  112. package/build/2517.5cc235ba.chunk.js +0 -117
  113. package/build/4306.53359960.chunk.js +0 -98
  114. package/build/8881.bfdb6877.chunk.js +0 -245
  115. package/build/9707.932a3c12.chunk.js +0 -70
  116. package/build/Admin-authenticatedApp.cfc3b4c9.chunk.js +0 -80
  117. package/build/admin-app.ee1211cb.chunk.js +0 -112
  118. package/build/api-tokens-create-page.4ca2778d.chunk.js +0 -1
  119. package/build/api-tokens-edit-page.70a791c2.chunk.js +0 -1
  120. package/build/content-manager.794d3373.chunk.js +0 -1200
  121. package/build/content-type-builder-list-view.95012cf0.chunk.js +0 -201
  122. package/build/content-type-builder.95b9d6a2.chunk.js +0 -145
  123. package/build/email-settings-page.4bb3606f.chunk.js +0 -15
  124. package/build/main.a6470578.js +0 -2031
  125. package/build/ru-json.d7cfc2ff.chunk.js +0 -1
  126. package/build/upload-settings.3010911f.chunk.js +0 -18
  127. package/build/upload.9f19f2e8.chunk.js +0 -64
  128. package/build/users-advanced-settings-page.9df41d67.chunk.js +0 -13
  129. package/build/users-email-settings-page.56d82eaf.chunk.js +0 -28
  130. package/build/users-providers-settings-page.96bb7da0.chunk.js +0 -33
  131. package/build/users-roles-settings-page.445e5e16.chunk.js +0 -30
  132. package/build/webhook-edit-page.c5efc08b.chunk.js +0 -75
@@ -1,24 +1,22 @@
1
- import React, { memo, useCallback, useMemo, useState } from 'react';
2
1
  /* eslint-disable import/no-cycle */
3
- import { useDrop } from 'react-dnd';
2
+ import React, { memo, useMemo, useState } from 'react';
4
3
  import { useIntl } from 'react-intl';
5
4
  import styled from 'styled-components';
6
5
  import PropTypes from 'prop-types';
7
6
  import get from 'lodash/get';
8
- import { useNotification } from '@strapi/helper-plugin';
9
- import { Box } from '@strapi/design-system/Box';
10
- import { Flex } from '@strapi/design-system/Flex';
11
- import { TextButton } from '@strapi/design-system/TextButton';
12
- import Plus from '@strapi/icons/Plus';
7
+
8
+ import { useNotification, useCMEditViewDataManager } from '@strapi/helper-plugin';
9
+ import { Box, Flex, TextButton, VisuallyHidden } from '@strapi/design-system';
10
+ import { Plus } from '@strapi/icons';
11
+
13
12
  import { getMaxTempKey, getTrad } from '../../utils';
14
13
  import { useContentTypeLayout } from '../../hooks';
15
- import ItemTypes from '../../utils/ItemTypes';
14
+
16
15
  import ComponentInitializer from '../ComponentInitializer';
17
- import connect from './utils/connect';
18
- import select from './utils/select';
16
+ import Component from './components/Component';
17
+ import * as Accordion from './components/Accordion';
18
+
19
19
  import getComponentErrorKeys from './utils/getComponentErrorKeys';
20
- import DraggedItem from './DraggedItem';
21
- import AccordionGroupCustom from './AccordionGroupCustom';
22
20
 
23
21
  const TextButtonCustom = styled(TextButton)`
24
22
  height: 100%;
@@ -33,8 +31,6 @@ const TextButtonCustom = styled(TextButton)`
33
31
  `;
34
32
 
35
33
  const RepeatableComponent = ({
36
- addRepeatableComponentToField,
37
- formErrors,
38
34
  componentUid,
39
35
  componentValue,
40
36
  componentValueLength,
@@ -43,11 +39,12 @@ const RepeatableComponent = ({
43
39
  min,
44
40
  name,
45
41
  }) => {
42
+ const { addRepeatableComponentToField, formErrors, moveComponentField } =
43
+ useCMEditViewDataManager();
46
44
  const toggleNotification = useNotification();
47
45
  const { formatMessage } = useIntl();
48
46
  const [collapseToOpen, setCollapseToOpen] = useState('');
49
- const [isDraggingSibling, setIsDraggingSibling] = useState(false);
50
- const [, drop] = useDrop({ accept: ItemTypes.COMPONENT });
47
+ const [liveText, setLiveText] = useState('');
51
48
  const { getComponentLayout, components } = useContentTypeLayout();
52
49
  const componentLayoutData = useMemo(
53
50
  () => getComponentLayout(componentUid),
@@ -60,15 +57,15 @@ const RepeatableComponent = ({
60
57
 
61
58
  const componentErrorKeys = getComponentErrorKeys(name, formErrors);
62
59
 
63
- const toggleCollapses = () => {
64
- setCollapseToOpen('');
65
- };
66
-
67
60
  const missingComponentsValue = min - componentValueLength;
68
61
 
69
62
  const hasMinError = get(formErrors, name, { id: '' }).id.includes('min');
70
63
 
71
- const handleClick = useCallback(() => {
64
+ const toggleCollapses = () => {
65
+ setCollapseToOpen('');
66
+ };
67
+
68
+ const handleClick = () => {
72
69
  if (!isReadOnly) {
73
70
  if (componentValueLength < max) {
74
71
  const shouldCheckErrors = hasMinError;
@@ -83,18 +80,89 @@ const RepeatableComponent = ({
83
80
  });
84
81
  }
85
82
  }
86
- }, [
87
- components,
88
- addRepeatableComponentToField,
89
- componentLayoutData,
90
- componentValueLength,
91
- hasMinError,
92
- isReadOnly,
93
- max,
94
- name,
95
- nextTempKey,
96
- toggleNotification,
97
- ]);
83
+ };
84
+
85
+ const handleMoveComponentField = (newIndex, currentIndex) => {
86
+ setLiveText(
87
+ formatMessage(
88
+ {
89
+ id: getTrad('dnd.reorder'),
90
+ defaultMessage: '{item}, moved. New position in list: {position}.',
91
+ },
92
+ {
93
+ item: `${name}.${currentIndex}`,
94
+ position: getItemPos(newIndex),
95
+ }
96
+ )
97
+ );
98
+
99
+ moveComponentField({
100
+ name,
101
+ newIndex,
102
+ currentIndex,
103
+ });
104
+ };
105
+
106
+ const mainField = get(componentLayoutData, ['settings', 'mainField'], 'id');
107
+
108
+ const handleToggle = (key) => () => {
109
+ if (collapseToOpen === key) {
110
+ setCollapseToOpen('');
111
+ } else {
112
+ setCollapseToOpen(key);
113
+ }
114
+ };
115
+
116
+ /**
117
+ *
118
+ * @param {number} index
119
+ * @returns {string}
120
+ */
121
+ const getItemPos = (index) => `${index + 1} of ${componentValueLength}`;
122
+
123
+ const handleCancel = (index) => {
124
+ setLiveText(
125
+ formatMessage(
126
+ {
127
+ id: getTrad('dnd.cancel-item'),
128
+ defaultMessage: '{item}, dropped. Re-order cancelled.',
129
+ },
130
+ {
131
+ item: `${name}.${index}`,
132
+ }
133
+ )
134
+ );
135
+ };
136
+
137
+ const handleGrabItem = (index) => {
138
+ setLiveText(
139
+ formatMessage(
140
+ {
141
+ id: getTrad('dnd.grab-item'),
142
+ defaultMessage: `{item}, grabbed. Current position in list: {position}. Press up and down arrow to change position, Spacebar to drop, Escape to cancel.`,
143
+ },
144
+ {
145
+ item: `${name}.${index}`,
146
+ position: getItemPos(index),
147
+ }
148
+ )
149
+ );
150
+ };
151
+
152
+ const handleDropItem = (index) => {
153
+ setLiveText(
154
+ formatMessage(
155
+ {
156
+ id: getTrad('dnd.drop-item'),
157
+ defaultMessage: `{item}, dropped. Final position in list: {position}.`,
158
+ },
159
+ {
160
+ item: `${name}.${index}`,
161
+ position: getItemPos(index),
162
+ }
163
+ )
164
+ );
165
+ };
98
166
 
99
167
  let errorMessage = formErrors[name];
100
168
 
@@ -105,6 +173,11 @@ const RepeatableComponent = ({
105
173
  'There {number, plural, =0 {are # missing components} one {is # missing component} other {are # missing components}}',
106
174
  values: { number: missingComponentsValue },
107
175
  };
176
+ } else if (componentErrorKeys.some((error) => error.split('.').length > 1) && !hasMinError) {
177
+ errorMessage = {
178
+ id: getTrad('components.RepeatableComponent.error-message'),
179
+ defaultMessage: 'The component(s) contain error(s)',
180
+ };
108
181
  }
109
182
 
110
183
  if (componentValueLength === 0) {
@@ -113,22 +186,44 @@ const RepeatableComponent = ({
113
186
  );
114
187
  }
115
188
 
116
- const doesRepComponentHasChildError = componentErrorKeys.some(
117
- (error) => error.split('.').length > 1
118
- );
119
-
120
- if (doesRepComponentHasChildError && !hasMinError) {
121
- errorMessage = {
122
- id: getTrad('components.RepeatableComponent.error-message'),
123
- defaultMessage: 'The component(s) contain error(s)',
124
- };
125
- }
189
+ const ariaDescriptionId = `${name}-item-instructions`;
126
190
 
127
191
  return (
128
- <Box hasRadius ref={drop}>
129
- <AccordionGroupCustom
130
- error={errorMessage}
131
- footer={
192
+ <Box hasRadius>
193
+ <VisuallyHidden id={ariaDescriptionId}>
194
+ {formatMessage({
195
+ id: getTrad('dnd.instructions'),
196
+ defaultMessage: `Press spacebar to grab and re-order`,
197
+ })}
198
+ </VisuallyHidden>
199
+ <VisuallyHidden aria-live="assertive">{liveText}</VisuallyHidden>
200
+ <Accordion.Group error={errorMessage} ariaDescribedBy={ariaDescriptionId}>
201
+ <Accordion.Content aria-describedby={ariaDescriptionId}>
202
+ {componentValue.map((data, index) => {
203
+ const key = data.__temp_key__;
204
+ const componentFieldName = `${name}.${index}`;
205
+
206
+ return (
207
+ <Component
208
+ componentFieldName={componentFieldName}
209
+ componentUid={componentUid}
210
+ fields={componentLayoutData.layouts.edit}
211
+ key={key}
212
+ index={index}
213
+ isOpen={collapseToOpen === key}
214
+ isReadOnly={isReadOnly}
215
+ mainField={mainField}
216
+ moveComponentField={handleMoveComponentField}
217
+ onClickToggle={handleToggle(key)}
218
+ toggleCollapses={toggleCollapses}
219
+ onCancel={handleCancel}
220
+ onDropItem={handleDropItem}
221
+ onGrabItem={handleGrabItem}
222
+ />
223
+ );
224
+ })}
225
+ </Accordion.Content>
226
+ <Accordion.Footer>
132
227
  <Flex justifyContent="center" height="48px" background="neutral0">
133
228
  <TextButtonCustom disabled={isReadOnly} onClick={handleClick} startIcon={<Plus />}>
134
229
  {formatMessage({
@@ -137,39 +232,8 @@ const RepeatableComponent = ({
137
232
  })}
138
233
  </TextButtonCustom>
139
234
  </Flex>
140
- }
141
- >
142
- {componentValue.map((data, index) => {
143
- const key = data.__temp_key__;
144
- const isOpen = collapseToOpen === key;
145
- const componentFieldName = `${name}.${index}`;
146
- const hasErrors = componentErrorKeys.includes(componentFieldName);
147
-
148
- return (
149
- <DraggedItem
150
- componentFieldName={componentFieldName}
151
- componentUid={componentUid}
152
- hasErrors={hasErrors}
153
- hasMinError={hasMinError}
154
- isDraggingSibling={isDraggingSibling}
155
- isOpen={isOpen}
156
- isReadOnly={isReadOnly}
157
- key={key}
158
- onClickToggle={() => {
159
- if (isOpen) {
160
- setCollapseToOpen('');
161
- } else {
162
- setCollapseToOpen(key);
163
- }
164
- }}
165
- parentName={name}
166
- schema={componentLayoutData}
167
- setIsDraggingSibling={setIsDraggingSibling}
168
- toggleCollapses={toggleCollapses}
169
- />
170
- );
171
- })}
172
- </AccordionGroupCustom>
235
+ </Accordion.Footer>
236
+ </Accordion.Group>
173
237
  </Box>
174
238
  );
175
239
  };
@@ -177,25 +241,21 @@ const RepeatableComponent = ({
177
241
  RepeatableComponent.defaultProps = {
178
242
  componentValue: null,
179
243
  componentValueLength: 0,
180
- formErrors: {},
244
+ isReadOnly: false,
181
245
  max: Infinity,
182
246
  min: 0,
183
247
  };
184
248
 
185
249
  RepeatableComponent.propTypes = {
186
- addRepeatableComponentToField: PropTypes.func.isRequired,
187
250
  componentUid: PropTypes.string.isRequired,
188
251
  componentValue: PropTypes.oneOfType([PropTypes.array, PropTypes.object]),
189
252
  componentValueLength: PropTypes.number,
190
- formErrors: PropTypes.object,
191
- isReadOnly: PropTypes.bool.isRequired,
253
+ isReadOnly: PropTypes.bool,
192
254
  max: PropTypes.number,
193
255
  min: PropTypes.number,
194
256
  name: PropTypes.string.isRequired,
195
257
  };
196
258
 
197
- const Memoized = memo(RepeatableComponent);
198
-
199
- export default connect(Memoized, select);
259
+ export default memo(RepeatableComponent);
200
260
 
201
261
  export { RepeatableComponent };
@@ -1,4 +1,4 @@
1
- export default function getComponentErrorKeys(name, formErrors) {
1
+ export default function getComponentErrorKeys(name, formErrors = {}) {
2
2
  return Object.keys(formErrors)
3
3
  .filter((errorKey) => errorKey.startsWith(name))
4
4
  .map((errorKey) =>
@@ -103,7 +103,7 @@ const SingleTypeFormWrapper = ({ allLayoutData, children, slug }) => {
103
103
  setIsCreatingEntry(true);
104
104
 
105
105
  try {
106
- const { data } = await axiosInstance(getRequestUrl(`${slug}${searchToSend}`), {
106
+ const { data } = await axiosInstance.get(getRequestUrl(`${slug}${searchToSend}`), {
107
107
  cancelToken: source.token,
108
108
  });
109
109
 
@@ -43,7 +43,7 @@ const Editor = ({
43
43
  }, [editorRef, textareaRef, name, placeholder]);
44
44
 
45
45
  useEffect(() => {
46
- if (value && !editorRef.current.state.focused) {
46
+ if (value && !editorRef.current.hasFocus()) {
47
47
  editorRef.current.setValue(value);
48
48
  }
49
49
  }, [editorRef, value]);
@@ -6,3 +6,5 @@ export { default as usePluginsQueryParams } from './usePluginsQueryParams';
6
6
  export { default as useSyncRbac } from './useSyncRbac';
7
7
  export { default as useWysiwyg } from './useWysiwyg';
8
8
  export { usePrev } from './usePrev';
9
+ export { useDragAndDrop } from './useDragAndDrop';
10
+ export { useKeyboardDragAndDrop } from './useKeyboardDragAndDrop';
@@ -0,0 +1,120 @@
1
+ import { useRef } from 'react';
2
+ import { useDrag, useDrop } from 'react-dnd';
3
+
4
+ import { useKeyboardDragAndDrop } from './useKeyboardDragAndDrop';
5
+
6
+ /**
7
+ * @typedef UseDragAndDropOptions
8
+ *
9
+ * @type {{
10
+ * type?: string,
11
+ * index: number,
12
+ * item?: object,
13
+ * onStart?: () => void,
14
+ * onEnd?: () => void,
15
+ * } & import('./useKeyboardDragAndDrop').UseKeyboardDragAndDropCallbacks}
16
+ */
17
+
18
+ /**
19
+ * @typedef UseDragAndDropReturn
20
+ *
21
+ * @type {[props: {handlerId: import('dnd-core').Identifier, isDragging: boolean, handleKeyDown: (event: import('react').KeyboardEvent<HTMLButtonElement>) => void}, objectRef: React.RefObject<HTMLElement>, dropRef: import('react-dnd').ConnectDropTarget, dragRef: import('react-dnd').ConnectDragSource, dragPreviewRef: import('react-dnd').ConnectDragPreview]}
22
+ */
23
+
24
+ /**
25
+ * A utility hook abstracting the general drag and drop hooks from react-dnd.
26
+ * Centralising the same behaviours and by default offering keyboard support.
27
+ *
28
+ * @type {(active: boolean, options: UseDragAndDropOptions) => UseDragAndDropReturn}
29
+ */
30
+ export const useDragAndDrop = (
31
+ active,
32
+ {
33
+ type = 'STRAPI_DND',
34
+ index,
35
+ item = {},
36
+ onStart,
37
+ onEnd,
38
+ onGrabItem,
39
+ onDropItem,
40
+ onCancel,
41
+ onMoveItem,
42
+ }
43
+ ) => {
44
+ const objectRef = useRef(null);
45
+
46
+ const [{ handlerId }, dropRef] = useDrop({
47
+ accept: type,
48
+ collect(monitor) {
49
+ return {
50
+ handlerId: monitor.getHandlerId(),
51
+ };
52
+ },
53
+ hover(item, monitor) {
54
+ if (!objectRef.current) {
55
+ return;
56
+ }
57
+ const dragIndex = item.index;
58
+ const newInd = index;
59
+
60
+ // Don't replace items with themselves
61
+ if (dragIndex === newInd) {
62
+ return;
63
+ }
64
+
65
+ const hoverBoundingRect = objectRef.current.getBoundingClientRect();
66
+ const hoverMiddleY = (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2;
67
+ const clientOffset = monitor.getClientOffset();
68
+ const hoverClientY = clientOffset.y - hoverBoundingRect.top;
69
+
70
+ // Dragging downwards
71
+ if (dragIndex < newInd && hoverClientY < hoverMiddleY) {
72
+ return;
73
+ }
74
+
75
+ // Dragging upwards
76
+ if (dragIndex > newInd && hoverClientY > hoverMiddleY) {
77
+ return;
78
+ }
79
+
80
+ // Time to actually perform the action
81
+ onMoveItem(newInd, dragIndex);
82
+ item.index = newInd;
83
+ },
84
+ });
85
+
86
+ const [{ isDragging }, dragRef, dragPreviewRef] = useDrag({
87
+ type,
88
+ item() {
89
+ if (onStart) {
90
+ onStart();
91
+ }
92
+
93
+ /**
94
+ * This will be attached and it helps define the preview sizes
95
+ * when a component is flexy e.g. Relations
96
+ */
97
+ const { width } = objectRef.current?.getBoundingClientRect() ?? {};
98
+
99
+ return { index, width, ...item };
100
+ },
101
+ end() {
102
+ if (onEnd) {
103
+ onEnd();
104
+ }
105
+ },
106
+ canDrag: active,
107
+ collect: (monitor) => ({
108
+ isDragging: monitor.isDragging(),
109
+ }),
110
+ });
111
+
112
+ const handleKeyDown = useKeyboardDragAndDrop(index, {
113
+ onGrabItem,
114
+ onDropItem,
115
+ onCancel,
116
+ onMoveItem,
117
+ });
118
+
119
+ return [{ handlerId, isDragging, handleKeyDown }, objectRef, dropRef, dragRef, dragPreviewRef];
120
+ };
@@ -0,0 +1,98 @@
1
+ import { useState } from 'react';
2
+
3
+ /**
4
+ * @typedef UseKeyboardDragAndDropCallbacks
5
+ *
6
+ * @type {{
7
+ * onCancel?: (index: number) => void,
8
+ * onDropItem?: (index: number) => void,
9
+ * onGrabItem?: (index: number) => void,
10
+ * onMoveItem: (newIndex: number, currentIndex: number) => void,
11
+ * }}
12
+ */
13
+
14
+ /**
15
+ * Utility hook designed to implement keyboard accessibile drag and drop by
16
+ * returning an onKeyDown handler to be passed to the drag icon button.
17
+ *
18
+ * @internal - You should use `useDragAndDrop` instead.
19
+ *
20
+ * @type {(index: number, callbacks: UseKeyboardDragAndDropCallbacks) => (event: React.KeyboardEvent<HTMLButtonElement>) => void}
21
+ */
22
+ export const useKeyboardDragAndDrop = (index, { onCancel, onDropItem, onGrabItem, onMoveItem }) => {
23
+ const [isSelected, setIsSelected] = useState(false);
24
+ /**
25
+ * @type {(movement: 'UP' | 'DOWN') => void})}
26
+ */
27
+ const handleMove = (movement) => {
28
+ if (!isSelected) {
29
+ return;
30
+ }
31
+
32
+ if (movement === 'UP') {
33
+ onMoveItem(index - 1, index);
34
+ } else if (movement === 'DOWN') {
35
+ onMoveItem(index + 1, index);
36
+ }
37
+ };
38
+
39
+ const handleDragClick = () => {
40
+ if (isSelected) {
41
+ if (onDropItem) {
42
+ onDropItem(index);
43
+ }
44
+ setIsSelected(false);
45
+ } else {
46
+ if (onGrabItem) {
47
+ onGrabItem(index);
48
+ }
49
+ setIsSelected(true);
50
+ }
51
+ };
52
+
53
+ const handleCancel = () => {
54
+ if (isSelected) {
55
+ setIsSelected(false);
56
+
57
+ if (onCancel) {
58
+ onCancel(index);
59
+ }
60
+ }
61
+ };
62
+
63
+ /**
64
+ * @type {React.KeyboardEventHandler<HTMLButtonElement>}
65
+ */
66
+ const handleKeyDown = (e) => {
67
+ if (e.key === 'Tab' && !isSelected) {
68
+ return;
69
+ }
70
+
71
+ e.preventDefault();
72
+
73
+ switch (e.key) {
74
+ case ' ':
75
+ case 'Enter':
76
+ handleDragClick();
77
+ break;
78
+
79
+ case 'Escape':
80
+ handleCancel();
81
+ break;
82
+
83
+ case 'ArrowDown':
84
+ case 'ArrowRight':
85
+ handleMove('DOWN');
86
+ break;
87
+
88
+ case 'ArrowUp':
89
+ case 'ArrowLeft':
90
+ handleMove('UP');
91
+ break;
92
+
93
+ default:
94
+ }
95
+ };
96
+
97
+ return handleKeyDown;
98
+ };
@@ -0,0 +1,69 @@
1
+ import { useCallback, useEffect, useState } from 'react';
2
+ import { useCustomFields } from '@strapi/helper-plugin';
3
+
4
+ const componentStore = new Map();
5
+
6
+ /**
7
+ * @description
8
+ * A hook to lazy load custom field components
9
+ * @param {Array.<string>} componentUids - The uids to look up components
10
+ * @returns object
11
+ */
12
+ const useLazyComponents = (componentUids = []) => {
13
+ const [lazyComponentStore, setLazyComponentStore] = useState(Object.fromEntries(componentStore));
14
+ const [loading, setLoading] = useState(() => {
15
+ if (componentStore.size === 0 && componentUids.length > 0) {
16
+ return true;
17
+ }
18
+
19
+ return false;
20
+ });
21
+ const customFieldsRegistry = useCustomFields();
22
+
23
+ useEffect(() => {
24
+ const setStore = (store) => {
25
+ setLazyComponentStore(store);
26
+ setLoading(false);
27
+ };
28
+
29
+ const lazyLoadComponents = async (uids, components) => {
30
+ const modules = await Promise.all(components);
31
+
32
+ uids.forEach((uid, index) => {
33
+ componentStore.set(uid, modules[index].default);
34
+ });
35
+
36
+ setStore(Object.fromEntries(componentStore));
37
+ };
38
+
39
+ if (componentUids.length && loading) {
40
+ /**
41
+ * These uids are not in the component store therefore we need to get the components
42
+ */
43
+ const newUids = componentUids.filter((uid) => !componentStore.get(uid));
44
+
45
+ const componentPromises = newUids.map((uid) => {
46
+ const customField = customFieldsRegistry.get(uid);
47
+
48
+ return customField.components.Input();
49
+ });
50
+
51
+ if (componentPromises.length > 0) {
52
+ lazyLoadComponents(newUids, componentPromises);
53
+ }
54
+ }
55
+ }, [componentUids, customFieldsRegistry, loading]);
56
+
57
+ /**
58
+ * Wrap this in a callback so it can be used in
59
+ * effects to cleanup the cached store if required
60
+ */
61
+ const cleanup = useCallback(() => {
62
+ componentStore.clear();
63
+ setLazyComponentStore({});
64
+ }, []);
65
+
66
+ return { isLazyLoading: loading, lazyComponentStore, cleanup };
67
+ };
68
+
69
+ 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