@strapi/admin 4.4.3 → 4.5.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/CollectionTypeFormWrapper/index.js +35 -1
  2. package/admin/src/content-manager/components/DynamicTable/CellContent/RelationMultiple/index.js +11 -7
  3. package/admin/src/content-manager/components/DynamicTable/CellContent/index.js +6 -5
  4. package/admin/src/content-manager/components/DynamicTable/TableRows/index.js +5 -0
  5. package/admin/src/content-manager/components/DynamicTable/index.js +1 -1
  6. package/admin/src/content-manager/components/EditViewDataManagerProvider/index.js +73 -20
  7. package/admin/src/content-manager/components/EditViewDataManagerProvider/reducer.js +166 -26
  8. package/admin/src/content-manager/components/EditViewDataManagerProvider/utils/cleanData.js +17 -1
  9. package/admin/src/content-manager/components/Inputs/index.js +12 -4
  10. package/admin/src/content-manager/components/NonRepeatableComponent/index.js +2 -0
  11. package/admin/src/content-manager/components/RelationInput/RelationInput.js +427 -0
  12. package/admin/src/content-manager/components/{SelectWrapper → RelationInput/components}/Option.js +15 -25
  13. package/admin/src/content-manager/components/RelationInput/components/Relation.js +48 -0
  14. package/admin/src/content-manager/components/RelationInput/components/RelationItem.js +52 -0
  15. package/admin/src/content-manager/components/RelationInput/components/RelationList.js +52 -0
  16. package/admin/src/content-manager/components/RelationInput/constants.js +1 -0
  17. package/admin/src/content-manager/components/RelationInput/index.js +1 -0
  18. package/admin/src/content-manager/components/RelationInputDataManager/RelationInputDataManager.js +261 -0
  19. package/admin/src/content-manager/components/RelationInputDataManager/constants.js +8 -0
  20. package/admin/src/content-manager/components/RelationInputDataManager/index.js +1 -0
  21. package/admin/src/content-manager/components/{SelectWrapper → RelationInputDataManager}/utils/connect.js +0 -1
  22. package/admin/src/content-manager/components/RelationInputDataManager/utils/getRelationLink.js +5 -0
  23. package/admin/src/content-manager/components/RelationInputDataManager/utils/index.js +4 -0
  24. package/admin/src/content-manager/components/RelationInputDataManager/utils/normalizeRelations.js +65 -0
  25. package/admin/src/content-manager/components/RelationInputDataManager/utils/normalizeSearchResults.js +12 -0
  26. package/admin/src/content-manager/components/RelationInputDataManager/utils/select.js +98 -0
  27. package/admin/src/content-manager/components/RepeatableComponent/DraggedItem/index.js +5 -0
  28. package/admin/src/content-manager/components/SingleTypeFormWrapper/index.js +42 -2
  29. package/admin/src/content-manager/hooks/useFetchContentTypeLayout/utils/formatLayouts.js +7 -69
  30. package/admin/src/content-manager/hooks/useRelation/index.js +1 -0
  31. package/admin/src/content-manager/hooks/useRelation/useRelation.js +81 -0
  32. package/admin/src/content-manager/pages/EditSettingsView/components/DisplayedFields.js +4 -4
  33. package/admin/src/content-manager/pages/EditSettingsView/index.js +21 -49
  34. package/admin/src/content-manager/pages/EditSettingsView/reducer.js +0 -25
  35. package/admin/src/content-manager/pages/EditView/Header/index.js +121 -140
  36. package/admin/src/content-manager/pages/EditView/Header/utils/index.js +0 -1
  37. package/admin/src/content-manager/pages/EditView/Header/utils/select.js +4 -2
  38. package/admin/src/content-manager/pages/EditView/index.js +12 -65
  39. package/admin/src/content-manager/pages/ListView/FieldPicker/index.js +0 -1
  40. package/admin/src/content-manager/pages/ListView/index.js +0 -1
  41. package/admin/src/content-manager/pages/ListViewLayoutManager/index.js +0 -1
  42. package/admin/src/content-manager/utils/formatLayoutToApi.js +1 -3
  43. package/admin/src/pages/HomePage/SocialLinks.js +1 -1
  44. package/admin/src/translations/ar.json +0 -1
  45. package/admin/src/translations/ca.json +1 -8
  46. package/admin/src/translations/cs.json +0 -2
  47. package/admin/src/translations/de.json +4 -12
  48. package/admin/src/translations/dk.json +3 -11
  49. package/admin/src/translations/en.json +4 -11
  50. package/admin/src/translations/es.json +156 -168
  51. package/admin/src/translations/fr.json +3 -11
  52. package/admin/src/translations/gu.json +608 -617
  53. package/admin/src/translations/hi.json +689 -698
  54. package/admin/src/translations/hu.json +2 -10
  55. package/admin/src/translations/id.json +2 -10
  56. package/admin/src/translations/it.json +2 -10
  57. package/admin/src/translations/ja.json +2 -11
  58. package/admin/src/translations/ko.json +2 -11
  59. package/admin/src/translations/ml.json +689 -698
  60. package/admin/src/translations/ms.json +0 -2
  61. package/admin/src/translations/nl.json +3 -11
  62. package/admin/src/translations/pl.json +2 -11
  63. package/admin/src/translations/pt-BR.json +3 -11
  64. package/admin/src/translations/pt.json +0 -1
  65. package/admin/src/translations/ru.json +489 -501
  66. package/admin/src/translations/sa.json +85 -93
  67. package/admin/src/translations/sk.json +3 -10
  68. package/admin/src/translations/sv.json +3 -9
  69. package/admin/src/translations/th.json +0 -2
  70. package/admin/src/translations/tr.json +0 -1
  71. package/admin/src/translations/uk.json +0 -2
  72. package/admin/src/translations/vi.json +0 -1
  73. package/admin/src/translations/zh-Hans.json +4 -13
  74. package/admin/src/translations/zh.json +3 -11
  75. package/build/{8773.51992277.chunk.js → 1939.e3c87653.chunk.js} +50 -50
  76. package/build/8738.a30a2160.chunk.js +461 -0
  77. package/build/962.8651ba3f.chunk.js +184 -0
  78. package/build/Admin-authenticatedApp.883449a5.chunk.js +80 -0
  79. package/build/{Admin_homePage.6d5e3236.chunk.js → Admin_homePage.4b2be829.chunk.js} +1 -1
  80. package/build/{ar-json.d4cb26d9.chunk.js → ar-json.3489463d.chunk.js} +1 -1
  81. package/build/{ca-json.d16c1d28.chunk.js → ca-json.82df6eab.chunk.js} +1 -1
  82. package/build/content-manager.933dc286.chunk.js +1201 -0
  83. package/build/{cs-json.c8f28ba8.chunk.js → cs-json.ce49da5c.chunk.js} +1 -1
  84. package/build/{de-json.a9b514dc.chunk.js → de-json.0ad554eb.chunk.js} +1 -1
  85. package/build/{dk-json.09e8d145.chunk.js → dk-json.e195ea1a.chunk.js} +1 -1
  86. package/build/{en-json.e936d40e.chunk.js → en-json.1889403c.chunk.js} +1 -1
  87. package/build/{es-json.3a9c7c09.chunk.js → es-json.09f80f6e.chunk.js} +1 -1
  88. package/build/{fr-json.4ed1fc2c.chunk.js → fr-json.606d056b.chunk.js} +1 -1
  89. package/build/{gu-json.d8311297.chunk.js → gu-json.9881264f.chunk.js} +1 -1
  90. package/build/{hi-json.0edb8d29.chunk.js → hi-json.83dcf48f.chunk.js} +1 -1
  91. package/build/{hu-json.7855529a.chunk.js → hu-json.6f328bce.chunk.js} +1 -1
  92. package/build/{id-json.df9618f2.chunk.js → id-json.1f3c4303.chunk.js} +1 -1
  93. package/build/index.html +1 -1
  94. package/build/{it-json.a21bf078.chunk.js → it-json.494ac432.chunk.js} +1 -1
  95. package/build/{ja-json.7b0d9067.chunk.js → ja-json.6f262117.chunk.js} +1 -1
  96. package/build/{ko-json.983c1f8f.chunk.js → ko-json.36dc3b9a.chunk.js} +1 -1
  97. package/build/main.63e7ea0a.js +9338 -0
  98. package/build/{ml-json.8dd021c8.chunk.js → ml-json.9566bf9a.chunk.js} +1 -1
  99. package/build/{ms-json.836ed013.chunk.js → ms-json.ed51e902.chunk.js} +1 -1
  100. package/build/{nl-json.29d2eb37.chunk.js → nl-json.94c3a289.chunk.js} +1 -1
  101. package/build/{pl-json.1f04f00c.chunk.js → pl-json.ccc6ef23.chunk.js} +1 -1
  102. package/build/{pt-BR-json.b4bc8efe.chunk.js → pt-BR-json.744f024d.chunk.js} +1 -1
  103. package/build/{pt-json.c23020ab.chunk.js → pt-json.3161ca22.chunk.js} +1 -1
  104. package/build/{ru-json.7ab40ccf.chunk.js → ru-json.d22ea13c.chunk.js} +1 -1
  105. package/build/{runtime~main.7faf633a.js → runtime~main.3a5e1b07.js} +1 -1
  106. package/build/{sa-json.c5a9f4ea.chunk.js → sa-json.8fb1c04d.chunk.js} +1 -1
  107. package/build/{sk-json.e4c24c4e.chunk.js → sk-json.6c7335d4.chunk.js} +1 -1
  108. package/build/{sv-json.c3f471ae.chunk.js → sv-json.2e589a7d.chunk.js} +1 -1
  109. package/build/{th-json.a59ffb32.chunk.js → th-json.72e8de3d.chunk.js} +1 -1
  110. package/build/{tr-json.276e59fe.chunk.js → tr-json.9c44ea0c.chunk.js} +1 -1
  111. package/build/{uk-json.5b5b9c27.chunk.js → uk-json.c4cd2e24.chunk.js} +1 -1
  112. package/build/{upload-translation-en-json.004a86c1.chunk.js → upload-translation-en-json.86da7b0a.chunk.js} +1 -1
  113. package/build/{vi-json.bf3424be.chunk.js → vi-json.f7890025.chunk.js} +1 -1
  114. package/build/{zh-Hans-json.9c99f8d4.chunk.js → zh-Hans-json.a4d7dc69.chunk.js} +1 -1
  115. package/build/{zh-json.451a0271.chunk.js → zh-json.66aa2ae1.chunk.js} +1 -1
  116. package/package.json +7 -7
  117. package/admin/src/content-manager/components/SelectMany/ListItem.js +0 -102
  118. package/admin/src/content-manager/components/SelectMany/index.js +0 -148
  119. package/admin/src/content-manager/components/SelectOne/SingleValue.js +0 -67
  120. package/admin/src/content-manager/components/SelectOne/index.js +0 -97
  121. package/admin/src/content-manager/components/SelectWrapper/Label.js +0 -60
  122. package/admin/src/content-manager/components/SelectWrapper/index.js +0 -356
  123. package/admin/src/content-manager/components/SelectWrapper/utils/index.js +0 -2
  124. package/admin/src/content-manager/components/SelectWrapper/utils/select.js +0 -45
  125. package/admin/src/content-manager/pages/EditSettingsView/components/RelationalFieldButton.js +0 -135
  126. package/admin/src/content-manager/pages/EditSettingsView/components/RelationalFields.js +0 -103
  127. package/admin/src/content-manager/pages/EditView/Header/utils/getDraftRelations.js +0 -62
  128. package/build/1669.d1b29c28.chunk.js +0 -1
  129. package/build/524.8a540ac1.chunk.js +0 -644
  130. package/build/Admin-authenticatedApp.88fa40ac.chunk.js +0 -80
  131. package/build/content-manager.8bddf2e6.chunk.js +0 -1178
  132. package/build/main.6650d2e7.js +0 -9338
@@ -0,0 +1,52 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import styled from 'styled-components';
4
+ import { Box } from '@strapi/design-system/Box';
5
+ import { Stack } from '@strapi/design-system/Stack';
6
+
7
+ const ShadowBox = styled(Box)`
8
+ position: relative;
9
+ overflow-x: hidden;
10
+ overflow-y: auto;
11
+
12
+ &:before,
13
+ &:after {
14
+ position: absolute;
15
+ width: 100%;
16
+ height: 4px;
17
+ z-index: 1;
18
+ }
19
+
20
+ &:before {
21
+ /* TODO: as for DS Table component we would need this to be handled by the DS theme */
22
+ content: ${({ overflowDirection }) =>
23
+ overflowDirection === 'top-bottom' || overflowDirection === 'top' ? "''" : undefined};
24
+ background: linear-gradient(rgba(33, 33, 52, 0.1) 0%, rgba(0, 0, 0, 0) 100%);
25
+ top: 0;
26
+ }
27
+
28
+ &:after {
29
+ /* TODO: as for DS Table component we would need this to be handled by the DS theme */
30
+ content: ${({ overflowDirection }) =>
31
+ overflowDirection === 'top-bottom' || overflowDirection === 'bottom' ? "''" : undefined};
32
+ background: linear-gradient(rgba(0, 0, 0, 0) 0%, rgba(33, 33, 52, 0.1) 100%);
33
+ bottom: 0;
34
+ }
35
+ `;
36
+
37
+ export const RelationList = ({ children, overflow, ...props }) => {
38
+ return (
39
+ <ShadowBox overflowDirection={overflow} {...props}>
40
+ <Stack spacing={1}>{children}</Stack>
41
+ </ShadowBox>
42
+ );
43
+ };
44
+
45
+ RelationList.defaultProps = {
46
+ overflow: '',
47
+ };
48
+
49
+ RelationList.propTypes = {
50
+ children: PropTypes.node.isRequired,
51
+ overflow: PropTypes.oneOf(['top-bottom', 'bottom', 'top', '']),
52
+ };
@@ -0,0 +1 @@
1
+ export const RELATION_ITEM_HEIGHT = 50;
@@ -0,0 +1 @@
1
+ export { default as RelationInput } from './RelationInput';
@@ -0,0 +1,261 @@
1
+ import PropTypes from 'prop-types';
2
+ import React, { memo, useEffect, useMemo } from 'react';
3
+ import { useIntl } from 'react-intl';
4
+ import get from 'lodash/get';
5
+
6
+ import { useCMEditViewDataManager, NotAllowedInput } from '@strapi/helper-plugin';
7
+
8
+ import { RelationInput } from '../RelationInput';
9
+ import { useRelation } from '../../hooks/useRelation';
10
+ import { connect, select, normalizeRelations, normalizeSearchResults } from './utils';
11
+ import { PUBLICATION_STATES, RELATIONS_TO_DISPLAY, SEARCH_RESULTS_TO_DISPLAY } from './constants';
12
+ import { getTrad } from '../../utils';
13
+
14
+ export const RelationInputDataManager = ({
15
+ componentId,
16
+ editable,
17
+ description,
18
+ intlLabel,
19
+ isCreatingEntry,
20
+ isFieldAllowed,
21
+ isFieldReadable,
22
+ labelAction,
23
+ mainField,
24
+ name,
25
+ queryInfos: { endpoints, defaultParams, shouldDisplayRelationLink },
26
+ placeholder,
27
+ required,
28
+ relationType,
29
+ size,
30
+ targetModel,
31
+ }) => {
32
+ const { formatMessage } = useIntl();
33
+ const { connectRelation, disconnectRelation, loadRelation, modifiedData, slug, initialData } =
34
+ useCMEditViewDataManager();
35
+
36
+ const { relations, search, searchFor } = useRelation(`${slug}-${name}-${initialData?.id ?? ''}`, {
37
+ relation: {
38
+ enabled: get(initialData, name)?.count !== 0 && !!endpoints.relation,
39
+ endpoint: endpoints.relation,
40
+ pageParams: {
41
+ ...defaultParams,
42
+ pageSize: RELATIONS_TO_DISPLAY,
43
+ },
44
+ },
45
+
46
+ search: {
47
+ endpoint: endpoints.search,
48
+ pageParams: {
49
+ ...defaultParams,
50
+ entityId: isCreatingEntry ? undefined : componentId ?? initialData.id,
51
+ pageSize: SEARCH_RESULTS_TO_DISPLAY,
52
+ },
53
+ },
54
+ });
55
+
56
+ const relationsFromModifiedData = get(modifiedData, name);
57
+ const stringifiedRelations = JSON.stringify(relations);
58
+ const normalizedRelations = useMemo(
59
+ () =>
60
+ normalizeRelations(relations, {
61
+ modifiedData: relationsFromModifiedData,
62
+ mainFieldName: mainField.name,
63
+ shouldAddLink: shouldDisplayRelationLink,
64
+ targetModel,
65
+ }),
66
+ // eslint-disable-next-line react-hooks/exhaustive-deps
67
+ [
68
+ stringifiedRelations,
69
+ modifiedData,
70
+ name,
71
+ mainField.name,
72
+ shouldDisplayRelationLink,
73
+ targetModel,
74
+ ]
75
+ );
76
+
77
+ useEffect(() => {
78
+ if (relations.status === 'success') {
79
+ loadRelation({
80
+ target: { name, value: normalizedRelations.data.pages.flat() },
81
+ });
82
+ }
83
+ // eslint-disable-next-line react-hooks/exhaustive-deps
84
+ }, [loadRelation, relations.status, stringifiedRelations, name]);
85
+
86
+ const isMorph = useMemo(() => relationType.toLowerCase().includes('morph'), [relationType]);
87
+ const isSingleRelation = [
88
+ 'oneWay',
89
+ 'oneToOne',
90
+ 'manyToOne',
91
+ 'oneToManyMorph',
92
+ 'oneToOneMorph',
93
+ ].includes(relationType);
94
+
95
+ const isDisabled = useMemo(() => {
96
+ if (isMorph) {
97
+ return true;
98
+ }
99
+
100
+ if (!isCreatingEntry) {
101
+ return (!isFieldAllowed && isFieldReadable) || !editable;
102
+ }
103
+
104
+ return !editable;
105
+ }, [isMorph, isCreatingEntry, editable, isFieldAllowed, isFieldReadable]);
106
+
107
+ const handleRelationAdd = (relation) => {
108
+ connectRelation({ target: { name, value: relation, replace: isSingleRelation } });
109
+ };
110
+
111
+ const handleRelationRemove = (relation) => {
112
+ disconnectRelation({ target: { name, value: relation } });
113
+ };
114
+
115
+ const handleRelationLoadMore = () => {
116
+ relations.fetchNextPage();
117
+ };
118
+
119
+ const handleSearch = (term) => {
120
+ searchFor(term, {
121
+ idsToInclude: relationsFromModifiedData?.disconnect?.map((relation) => relation.id),
122
+ idsToOmit: relationsFromModifiedData?.connect?.map((relation) => relation.id),
123
+ });
124
+ };
125
+
126
+ const handleOpenSearch = () => {
127
+ searchFor('', {
128
+ idsToInclude: relationsFromModifiedData?.disconnect?.map((relation) => relation.id),
129
+ idsToOmit: relationsFromModifiedData?.connect?.map((relation) => relation.id),
130
+ });
131
+ };
132
+
133
+ const handleSearchMore = () => {
134
+ search.fetchNextPage();
135
+ };
136
+
137
+ if (
138
+ (!isFieldAllowed && isCreatingEntry) ||
139
+ (!isCreatingEntry && !isFieldAllowed && !isFieldReadable)
140
+ ) {
141
+ return <NotAllowedInput intlLabel={intlLabel} labelAction={labelAction} />;
142
+ }
143
+
144
+ return (
145
+ <RelationInput
146
+ description={description}
147
+ disabled={isDisabled}
148
+ id={name}
149
+ label={`${formatMessage({
150
+ id: intlLabel.id,
151
+ defaultMessage: intlLabel.defaultMessage,
152
+ })} ${initialData[name]?.count !== undefined ? `(${initialData[name].count})` : ''}`}
153
+ labelAction={labelAction}
154
+ labelLoadMore={
155
+ !isCreatingEntry
156
+ ? formatMessage({
157
+ id: getTrad('relation.loadMore'),
158
+ defaultMessage: 'Load More',
159
+ })
160
+ : null
161
+ }
162
+ labelDisconnectRelation={formatMessage({
163
+ id: getTrad('relation.disconnect'),
164
+ defaultMessage: 'Remove',
165
+ })}
166
+ listHeight={320}
167
+ loadingMessage={() =>
168
+ formatMessage({
169
+ id: getTrad('relation.isLoading'),
170
+ defaultMessage: 'Relations are loading',
171
+ })
172
+ }
173
+ name={name}
174
+ numberOfRelationsToDisplay={RELATIONS_TO_DISPLAY}
175
+ onRelationAdd={(relation) => handleRelationAdd(relation)}
176
+ onRelationRemove={(relation) => handleRelationRemove(relation)}
177
+ onRelationLoadMore={() => handleRelationLoadMore()}
178
+ onSearch={(term) => handleSearch(term)}
179
+ onSearchNextPage={() => handleSearchMore()}
180
+ onSearchOpen={handleOpenSearch}
181
+ placeholder={formatMessage(
182
+ placeholder || {
183
+ id: getTrad('relation.add'),
184
+ defaultMessage: 'Add relation',
185
+ }
186
+ )}
187
+ publicationStateTranslations={{
188
+ [PUBLICATION_STATES.DRAFT]: formatMessage({
189
+ id: getTrad('relation.publicationState.draft'),
190
+ defaultMessage: 'Draft',
191
+ }),
192
+
193
+ [PUBLICATION_STATES.PUBLISHED]: formatMessage({
194
+ id: getTrad('relation.publicationState.published'),
195
+ defaultMessage: 'Published',
196
+ }),
197
+ }}
198
+ relations={normalizedRelations}
199
+ required={required}
200
+ searchResults={normalizeSearchResults(search, {
201
+ mainFieldName: mainField.name,
202
+ })}
203
+ size={size}
204
+ />
205
+ );
206
+ };
207
+
208
+ RelationInputDataManager.defaultProps = {
209
+ componentId: undefined,
210
+ editable: true,
211
+ description: '',
212
+ labelAction: null,
213
+ isFieldAllowed: true,
214
+ placeholder: null,
215
+ required: false,
216
+ };
217
+
218
+ RelationInputDataManager.propTypes = {
219
+ componentId: PropTypes.number,
220
+ editable: PropTypes.bool,
221
+ description: PropTypes.string,
222
+ intlLabel: PropTypes.shape({
223
+ id: PropTypes.string.isRequired,
224
+ defaultMessage: PropTypes.string.isRequired,
225
+ values: PropTypes.object,
226
+ }).isRequired,
227
+ labelAction: PropTypes.element,
228
+ isCreatingEntry: PropTypes.bool.isRequired,
229
+ isFieldAllowed: PropTypes.bool,
230
+ isFieldReadable: PropTypes.bool.isRequired,
231
+ mainField: PropTypes.shape({
232
+ name: PropTypes.string.isRequired,
233
+ schema: PropTypes.shape({
234
+ type: PropTypes.string.isRequired,
235
+ }).isRequired,
236
+ }).isRequired,
237
+ name: PropTypes.string.isRequired,
238
+ placeholder: PropTypes.shape({
239
+ id: PropTypes.string.isRequired,
240
+ defaultMessage: PropTypes.string.isRequired,
241
+ values: PropTypes.object,
242
+ }),
243
+ required: PropTypes.bool,
244
+ relationType: PropTypes.string.isRequired,
245
+ size: PropTypes.number.isRequired,
246
+ targetModel: PropTypes.string.isRequired,
247
+ queryInfos: PropTypes.shape({
248
+ defaultParams: PropTypes.shape({
249
+ _component: PropTypes.string,
250
+ }),
251
+ endpoints: PropTypes.shape({
252
+ relation: PropTypes.string,
253
+ search: PropTypes.string.isRequired,
254
+ }).isRequired,
255
+ shouldDisplayRelationLink: PropTypes.bool.isRequired,
256
+ }).isRequired,
257
+ };
258
+
259
+ const Memoized = memo(RelationInputDataManager);
260
+
261
+ export default connect(Memoized, select);
@@ -0,0 +1,8 @@
1
+ export const PUBLICATION_STATES = {
2
+ DRAFT: 'draft',
3
+ PUBLISHED: 'published',
4
+ };
5
+
6
+ export const RELATIONS_TO_DISPLAY = 5;
7
+
8
+ export const SEARCH_RESULTS_TO_DISPLAY = 10;
@@ -0,0 +1 @@
1
+ export { default as RelationInputDataManager } from './RelationInputDataManager';
@@ -2,7 +2,6 @@ import React from 'react';
2
2
 
3
3
  function connect(WrappedComponent, select) {
4
4
  return (props) => {
5
- // eslint-disable-next-line react/prop-types
6
5
  const selectors = select(props);
7
6
 
8
7
  return <WrappedComponent {...props} {...selectors} />;
@@ -0,0 +1,5 @@
1
+ import { getRequestUrl } from '../../../utils';
2
+
3
+ export function getRelationLink(targetModel, id) {
4
+ return `${getRequestUrl(`collectionType/${targetModel}/${id ?? ''}`)}`;
5
+ }
@@ -0,0 +1,4 @@
1
+ export { default as connect } from './connect';
2
+ export { default as select } from './select';
3
+ export { normalizeRelations } from './normalizeRelations';
4
+ export { normalizeSearchResults } from './normalizeSearchResults';
@@ -0,0 +1,65 @@
1
+ import { getRelationLink } from './getRelationLink';
2
+
3
+ import { PUBLICATION_STATES } from '../constants';
4
+
5
+ export const normalizeRelation = (relation, { shouldAddLink, mainFieldName, targetModel }) => {
6
+ const nextRelation = { ...relation };
7
+
8
+ if (shouldAddLink) {
9
+ nextRelation.href = getRelationLink(targetModel, nextRelation.id);
10
+ }
11
+
12
+ nextRelation.publicationState = false;
13
+
14
+ if (nextRelation?.publishedAt !== undefined) {
15
+ nextRelation.publicationState = nextRelation.publishedAt
16
+ ? PUBLICATION_STATES.PUBLISHED
17
+ : PUBLICATION_STATES.DRAFT;
18
+ }
19
+
20
+ nextRelation.mainField = nextRelation[mainFieldName];
21
+
22
+ return nextRelation;
23
+ };
24
+
25
+ /*
26
+ * Applies some transformations to existing and new relations in order to display them correctly
27
+ * relations: raw relations data coming from useRelations
28
+ * shouldAddLink: comes from generateRelationQueryInfos, if true we display a link to the relation (TO FIX: explanation)
29
+ * mainFieldName: name of the main field inside the relation (e.g. text field), if no displayable main field exists (e.g. date field) we use the id of the entry
30
+ * targetModel: the model on which the relation is based on, used to create an URL link
31
+ */
32
+
33
+ export const normalizeRelations = (
34
+ relations,
35
+ { modifiedData = {}, shouldAddLink = false, mainFieldName, targetModel }
36
+ ) => {
37
+ return {
38
+ ...relations,
39
+ data: {
40
+ pages:
41
+ [
42
+ ...(relations?.data?.pages.reverse() ?? []),
43
+ ...(modifiedData?.connect ? [{ results: modifiedData.connect }] : []),
44
+ ]
45
+ ?.map((page) =>
46
+ page?.results
47
+ .filter(
48
+ (relation) =>
49
+ !modifiedData?.disconnect?.find(
50
+ (disconnectRelation) => disconnectRelation.id === relation.id
51
+ )
52
+ )
53
+ .map((relation) =>
54
+ normalizeRelation(relation, {
55
+ shouldAddLink,
56
+ mainFieldName,
57
+ targetModel,
58
+ })
59
+ )
60
+ .filter(Boolean)
61
+ )
62
+ ?.filter((page) => page.length > 0) ?? [],
63
+ },
64
+ };
65
+ };
@@ -0,0 +1,12 @@
1
+ import { normalizeRelation } from './normalizeRelations';
2
+
3
+ export const normalizeSearchResults = (relations, { mainFieldName }) => {
4
+ return {
5
+ ...relations,
6
+ data: {
7
+ pages: [...(relations?.data?.pages ?? [])]?.map((page) =>
8
+ page?.results.map((relation) => normalizeRelation(relation, { mainFieldName }))
9
+ ),
10
+ },
11
+ };
12
+ };
@@ -0,0 +1,98 @@
1
+ import { useMemo } from 'react';
2
+ import get from 'lodash/get';
3
+ import { useCMEditViewDataManager } from '@strapi/helper-plugin';
4
+
5
+ import { getRequestUrl } from '../../../utils';
6
+
7
+ function useSelect({
8
+ componentUid,
9
+ isUserAllowedToEditField,
10
+ isUserAllowedToReadField,
11
+ name,
12
+ queryInfos,
13
+ }) {
14
+ const {
15
+ isCreatingEntry,
16
+ createActionAllowedFields,
17
+ readActionAllowedFields,
18
+ updateActionAllowedFields,
19
+ slug,
20
+ initialData,
21
+ } = useCMEditViewDataManager();
22
+
23
+ const isFieldAllowed = useMemo(() => {
24
+ if (isUserAllowedToEditField === true) {
25
+ return true;
26
+ }
27
+
28
+ const allowedFields = isCreatingEntry ? createActionAllowedFields : updateActionAllowedFields;
29
+
30
+ return allowedFields.includes(name);
31
+ }, [
32
+ isCreatingEntry,
33
+ createActionAllowedFields,
34
+ name,
35
+ isUserAllowedToEditField,
36
+ updateActionAllowedFields,
37
+ ]);
38
+
39
+ const isFieldReadable = useMemo(() => {
40
+ if (isUserAllowedToReadField) {
41
+ return true;
42
+ }
43
+
44
+ const allowedFields = isCreatingEntry ? [] : readActionAllowedFields;
45
+
46
+ return allowedFields.includes(name);
47
+ }, [isCreatingEntry, isUserAllowedToReadField, name, readActionAllowedFields]);
48
+
49
+ const fieldNameKeys = name.split('.');
50
+ let componentId;
51
+
52
+ if (componentUid) {
53
+ componentId = get(initialData, fieldNameKeys.slice(0, -1))?.id;
54
+ }
55
+
56
+ // /content-manager/relations/[model]/[id]/[field-name]
57
+ const relationFetchEndpoint = useMemo(() => {
58
+ if (isCreatingEntry) {
59
+ return null;
60
+ }
61
+
62
+ if (componentUid) {
63
+ // repeatable components and dz are dynamically created
64
+ // if no componentId exists in initialData it means that the user just created it
65
+ // there then are no relations to request
66
+ return componentId
67
+ ? getRequestUrl(`relations/${componentUid}/${componentId}/${fieldNameKeys.at(-1)}`)
68
+ : null;
69
+ }
70
+
71
+ return getRequestUrl(`relations/${slug}/${initialData.id}/${name.split('.').at(-1)}`);
72
+ }, [isCreatingEntry, componentUid, slug, initialData.id, name, componentId, fieldNameKeys]);
73
+
74
+ // /content-manager/relations/[model]/[field-name]
75
+ const relationSearchEndpoint = useMemo(() => {
76
+ if (componentUid) {
77
+ return getRequestUrl(`relations/${componentUid}/${name.split('.').at(-1)}`);
78
+ }
79
+
80
+ return getRequestUrl(`relations/${slug}/${name.split('.').at(-1)}`);
81
+ }, [componentUid, slug, name]);
82
+
83
+ return {
84
+ componentId,
85
+ queryInfos: {
86
+ ...queryInfos,
87
+ endpoints: {
88
+ search: relationSearchEndpoint,
89
+ relation: relationFetchEndpoint,
90
+ },
91
+ },
92
+ isCreatingEntry,
93
+ isFieldAllowed,
94
+ isFieldReadable,
95
+ };
96
+ }
97
+
98
+ export default useSelect;
@@ -44,6 +44,7 @@ const DragButton = styled.span`
44
44
 
45
45
  const DraggedItem = ({
46
46
  componentFieldName,
47
+ componentUid,
47
48
  // Errors are retrieved from the AccordionGroupCustom cloneElement
48
49
  hasErrorMessage,
49
50
  hasErrors,
@@ -265,11 +266,13 @@ const DraggedItem = ({
265
266
  return (
266
267
  <GridItem key={keys} col={size} s={12} xs={12}>
267
268
  <Inputs
269
+ componentUid={componentUid}
268
270
  fieldSchema={fieldSchema}
269
271
  keys={keys}
270
272
  metadatas={metadatas}
271
273
  // onBlur={hasErrors ? checkFormErrors : null}
272
274
  queryInfos={queryInfos}
275
+ size={size}
273
276
  />
274
277
  </GridItem>
275
278
  );
@@ -286,6 +289,7 @@ const DraggedItem = ({
286
289
  };
287
290
 
288
291
  DraggedItem.defaultProps = {
292
+ componentUid: undefined,
289
293
  isDraggingSibling: false,
290
294
  isOpen: false,
291
295
  setIsDraggingSibling() {},
@@ -294,6 +298,7 @@ DraggedItem.defaultProps = {
294
298
 
295
299
  DraggedItem.propTypes = {
296
300
  componentFieldName: PropTypes.string.isRequired,
301
+ componentUid: PropTypes.string,
297
302
  hasErrorMessage: PropTypes.bool.isRequired,
298
303
  hasErrors: PropTypes.bool.isRequired,
299
304
  isDraggingSibling: PropTypes.bool,
@@ -1,5 +1,6 @@
1
1
  import { memo, useCallback, useEffect, useRef, useState } from 'react';
2
2
  import { useHistory } from 'react-router-dom';
3
+ import { useQueryClient } from 'react-query';
3
4
  import get from 'lodash/get';
4
5
  import {
5
6
  useTracking,
@@ -28,6 +29,7 @@ import buildQueryString from '../../pages/ListView/utils/buildQueryString';
28
29
 
29
30
  // This container is used to handle the CRUD
30
31
  const SingleTypeFormWrapper = ({ allLayoutData, children, slug }) => {
32
+ const queryClient = useQueryClient();
31
33
  const { trackUsage } = useTracking();
32
34
  const { push } = useHistory();
33
35
  const { setCurrentStep } = useGuidedTour();
@@ -202,6 +204,9 @@ const SingleTypeFormWrapper = ({ allLayoutData, children, slug }) => {
202
204
 
203
205
  setCurrentStep('contentManager.success');
204
206
 
207
+ // TODO: need to find a better place, or a better abstraction
208
+ queryClient.invalidateQueries(['relation']);
209
+
205
210
  dispatch(submitSucceeded(cleanReceivedData(data)));
206
211
  setIsCreatingEntry(false);
207
212
 
@@ -218,8 +223,39 @@ const SingleTypeFormWrapper = ({ allLayoutData, children, slug }) => {
218
223
  return Promise.reject(err);
219
224
  }
220
225
  },
221
- [cleanReceivedData, displayErrors, slug, dispatch, rawQuery, toggleNotification, setCurrentStep]
226
+ [
227
+ cleanReceivedData,
228
+ displayErrors,
229
+ slug,
230
+ dispatch,
231
+ rawQuery,
232
+ toggleNotification,
233
+ setCurrentStep,
234
+ queryClient,
235
+ ]
222
236
  );
237
+
238
+ const onDraftRelationCheck = useCallback(async () => {
239
+ try {
240
+ trackUsageRef.current('willCheckDraftRelations');
241
+
242
+ const endPoint = getRequestUrl(`${slug}/actions/numberOfDraftRelations`);
243
+ dispatch(setStatus('draft-relation-check-pending'));
244
+
245
+ const numberOfDraftRelations = await axiosInstance.get(endPoint);
246
+ trackUsageRef.current('didCheckDraftRelations');
247
+
248
+ dispatch(setStatus('resolved'));
249
+
250
+ return numberOfDraftRelations.data.data;
251
+ } catch (err) {
252
+ displayErrors(err);
253
+ dispatch(setStatus('resolved'));
254
+
255
+ return Promise.reject(err);
256
+ }
257
+ }, [displayErrors, slug, dispatch]);
258
+
223
259
  const onPublish = useCallback(async () => {
224
260
  try {
225
261
  trackUsageRef.current('willPublishEntry');
@@ -267,6 +303,9 @@ const SingleTypeFormWrapper = ({ allLayoutData, children, slug }) => {
267
303
 
268
304
  trackUsageRef.current('didEditEntry', { trackerProperty });
269
305
 
306
+ // TODO: need to find a better place, or a better abstraction
307
+ queryClient.invalidateQueries(['relation']);
308
+
270
309
  dispatch(submitSucceeded(cleanReceivedData(data)));
271
310
 
272
311
  dispatch(setStatus('resolved'));
@@ -282,7 +321,7 @@ const SingleTypeFormWrapper = ({ allLayoutData, children, slug }) => {
282
321
  return Promise.reject(err);
283
322
  }
284
323
  },
285
- [cleanReceivedData, displayErrors, slug, dispatch, rawQuery, toggleNotification]
324
+ [cleanReceivedData, displayErrors, slug, dispatch, rawQuery, toggleNotification, queryClient]
286
325
  );
287
326
 
288
327
  // The publish and unpublish method could be refactored but let's leave the duplication for now
@@ -320,6 +359,7 @@ const SingleTypeFormWrapper = ({ allLayoutData, children, slug }) => {
320
359
  onDelete,
321
360
  onDeleteSucceeded,
322
361
  onPost,
362
+ onDraftRelationCheck,
323
363
  onPublish,
324
364
  onPut,
325
365
  onUnpublish,