@webbio/strapi-plugin-page-builder 0.6.0-platform → 0.8.0-platform

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 (58) hide show
  1. package/admin/src/api/has-page-relation.ts +1 -1
  2. package/admin/src/api/has-platform-relation.ts +1 -1
  3. package/admin/src/api/platform-page-types.ts +17 -19
  4. package/admin/src/api/platform.ts +2 -2
  5. package/admin/src/api/search-filtered-entity.ts +111 -0
  6. package/admin/src/api/template.ts +10 -7
  7. package/admin/src/components/Combobox/index.tsx +8 -6
  8. package/admin/src/components/Combobox/styles.ts +13 -1
  9. package/admin/src/components/EditView/CollectionTypeSettings/CreatePageButton/index.tsx +6 -2
  10. package/admin/src/components/EditView/Details/index.tsx +1 -1
  11. package/admin/src/components/EditView/PageSettings/index.tsx +1 -1
  12. package/admin/src/components/EditView/Template/TemplateSelect/index.tsx +3 -2
  13. package/admin/src/components/EditView/Template/TemplateSelect/use-template-modules.ts +5 -2
  14. package/admin/src/components/EditView/page-type-select.tsx +1 -1
  15. package/admin/src/components/EditView/wrapper.tsx +13 -7
  16. package/admin/src/components/GlobalPlatformSelect/index.tsx +0 -1
  17. package/admin/src/components/PageFilters/PageTypeFilter/index.tsx +1 -1
  18. package/admin/src/components/PageFilters/filters.tsx +1 -1
  19. package/admin/src/components/PageFilters/index.tsx +6 -1
  20. package/admin/src/components/PageTypeEditView/PageByPlatformSelect/index.tsx +112 -0
  21. package/admin/src/components/PageTypeEditView/TemplatePlatformSelect/index.tsx +55 -0
  22. package/admin/src/components/PageTypeEditView/index.tsx +28 -0
  23. package/admin/src/components/PlatformFilteredSelectField/InputIcon/index.tsx +23 -0
  24. package/admin/src/components/PlatformFilteredSelectField/Multi/index.tsx +191 -0
  25. package/admin/src/components/PlatformFilteredSelectField/Single/index.tsx +184 -0
  26. package/admin/src/components/PlatformFilteredSelectField/hooks/useRelationLoad.tsx +124 -0
  27. package/admin/src/components/PlatformFilteredSelectField/index.tsx +43 -0
  28. package/admin/src/components/PlatformFilteredSelectField/styles.tsx +77 -0
  29. package/admin/src/components/PlatformFilteredSelectField/utils/get-translations.ts +3 -0
  30. package/admin/src/components/PlatformFilteredSelectField/utils/relation-helper.ts +76 -0
  31. package/admin/src/index.tsx +26 -0
  32. package/admin/src/translations/en.json +4 -1
  33. package/admin/src/translations/nl.json +4 -1
  34. package/admin/src/utils/getObjectFromFormName.ts +31 -0
  35. package/admin/src/utils/hooks/usePlatformFormData.ts +1 -0
  36. package/dist/package.json +3 -3
  37. package/dist/server/controllers/page-type.js +6 -2
  38. package/dist/server/register.js +5 -0
  39. package/dist/server/routes/index.js +1 -1
  40. package/dist/server/schema/page-type-end.json +15 -5
  41. package/dist/server/schema/platform-start.json +0 -10
  42. package/dist/server/schema/template-end.json +40 -0
  43. package/dist/server/services/builder.js +4 -3
  44. package/dist/server/services/page-type.js +7 -3
  45. package/dist/tsconfig.server.tsbuildinfo +1 -1
  46. package/package.json +3 -3
  47. package/server/controllers/page-type.ts +6 -1
  48. package/server/register.ts +6 -0
  49. package/server/routes/index.ts +1 -1
  50. package/server/schema/page-type-end.json +15 -5
  51. package/server/schema/platform-start.json +0 -10
  52. package/server/schema/template-end.json +40 -0
  53. package/server/services/builder.ts +4 -3
  54. package/server/services/page-type.ts +7 -3
  55. package/admin/src/api/page-type.ts +0 -32
  56. package/dist/server/graphql/page-by-slug.js +0 -82
  57. package/dist/server/utils/graphql.js +0 -100
  58. package/dist/server/utils/paginationValidation.js +0 -31
@@ -0,0 +1,55 @@
1
+ import React from 'react';
2
+
3
+ import { useCMEditViewDataManager } from '@strapi/helper-plugin';
4
+ import { SingleSelect, SingleSelectOption } from '@strapi/design-system';
5
+
6
+ import { useGetTemplates } from '../../../api/template';
7
+
8
+ export const TemplatePlatformSelect = () => {
9
+ const { onChange, modifiedData } = (useCMEditViewDataManager() || {}) as Record<string, any>;
10
+ const { data: templates } = useGetTemplates({ id: modifiedData?.platform?.[0]?.id });
11
+
12
+ const setFormValue = (name: string, value?: string | Record<string, any>[]) => {
13
+ onChange({
14
+ target: {
15
+ name,
16
+ value
17
+ },
18
+ shouldSetInitialValue: true
19
+ });
20
+ };
21
+
22
+ const handleSelectTemplate = (templateId: string) => {
23
+ const template = templates?.find((pageType) => pageType.id === Number(templateId));
24
+
25
+ if (template) {
26
+ const formTemplate = {
27
+ ...template,
28
+ mainField: template.title,
29
+ label: template.title,
30
+ value: template.id
31
+ };
32
+ setFormValue('template', [formTemplate]);
33
+ } else {
34
+ setFormValue('template', []);
35
+ }
36
+ };
37
+
38
+ return (
39
+ <SingleSelect
40
+ width="100%"
41
+ label="Template"
42
+ placeholder="Kies een template"
43
+ value={modifiedData?.template?.[0]?.id || 0}
44
+ onChange={handleSelectTemplate}
45
+ disabled={Boolean(!modifiedData?.platform?.[0]?.id)}
46
+ >
47
+ <SingleSelectOption value={0}>Geen template geselecteerd</SingleSelectOption>
48
+ {templates?.map((template) => (
49
+ <SingleSelectOption key={template.id} value={template.id}>
50
+ {template.title}
51
+ </SingleSelectOption>
52
+ ))}
53
+ </SingleSelect>
54
+ );
55
+ };
@@ -0,0 +1,28 @@
1
+ import React from 'react';
2
+
3
+ import { useCMEditViewDataManager } from '@strapi/helper-plugin';
4
+ import { Stack } from '@strapi/design-system';
5
+
6
+ import { PAGE_TYPE_UID } from '../../../../shared/utils/constants';
7
+ import { TemplatePlatformSelect } from './TemplatePlatformSelect';
8
+ import { Wrapper } from '../EditView/wrapper';
9
+ import { PageByPlatformSearch } from './PageByPlatformSelect';
10
+
11
+ export const PageTypeEditView = () => {
12
+ const { layout } = useCMEditViewDataManager() as any;
13
+
14
+ const isPageTypeCollectionType = layout.uid === PAGE_TYPE_UID;
15
+
16
+ if (isPageTypeCollectionType) {
17
+ return (
18
+ <Wrapper title="Instellingen">
19
+ <Stack spacing={4} width="100%">
20
+ <TemplatePlatformSelect />
21
+ <PageByPlatformSearch />
22
+ </Stack>
23
+ </Wrapper>
24
+ );
25
+ }
26
+
27
+ return null;
28
+ };
@@ -0,0 +1,23 @@
1
+ import React from 'react';
2
+ import styled from 'styled-components';
3
+ import { Icon } from '@strapi/design-system';
4
+ import { Text } from '@strapi/icons';
5
+
6
+ const IconBox = styled.div`
7
+ svg > rect {
8
+ fill: #0e4dff;
9
+ stroke: #0e4dff;
10
+ }
11
+
12
+ svg > path {
13
+ fill: #fff;
14
+ }
15
+ `;
16
+
17
+ const FieldIcon = () => (
18
+ <IconBox>
19
+ <Icon width={7} height={6} as={Text} />
20
+ </IconBox>
21
+ );
22
+
23
+ export default FieldIcon;
@@ -0,0 +1,191 @@
1
+ import React, { useEffect, useState } from 'react';
2
+ import { useIntl } from 'react-intl';
3
+ import { useCMEditViewDataManager } from '@strapi/helper-plugin';
4
+ import { useSelector } from 'react-redux';
5
+ import { useFetchClient } from '@strapi/helper-plugin';
6
+ import pick from 'lodash/pick';
7
+
8
+ import RelationInput from '@strapi/admin/admin/src/content-manager/components/RelationInput/RelationInput';
9
+
10
+ import { useGetLocaleFromUrl } from '../../../utils/hooks/useGetLocaleFromUrl';
11
+ import { getSearchFilteredEntities } from '../../../api/search-filtered-entity';
12
+ import RelationHelper, { IPlatformFilteredSelectFieldProps } from '../utils/relation-helper';
13
+ import { getTranslation } from '../utils/get-translations';
14
+
15
+ const PAGE = 1;
16
+
17
+ // This is largely a copy of https://github.com/strapi/strapi/blob/4e6961c7b8127f0f3bb0ad1fc430351ae9c4b8fa/packages/core/admin/admin/src/content-manager/components/Relations/RelationInputDataManager.tsx
18
+
19
+ export interface IMultiPlatformFilteredSelectFieldProps extends IPlatformFilteredSelectFieldProps {
20
+ relations?: any;
21
+ isCloningEntry?: boolean;
22
+ totalRelations: number;
23
+ relationsFromModifiedData?: Record<string, any>[];
24
+ }
25
+
26
+ const MultiPlatformFilteredSelectField = ({
27
+ attribute,
28
+ description,
29
+ labelAction,
30
+ error,
31
+ required,
32
+ placeholder,
33
+ intlLabel,
34
+ name,
35
+ relations,
36
+ isCloningEntry,
37
+ totalRelations,
38
+ relationsFromModifiedData
39
+ }: IMultiPlatformFilteredSelectFieldProps) => {
40
+ const fetchClient = useFetchClient();
41
+ const {
42
+ initialData,
43
+ modifiedData,
44
+ layout,
45
+ allLayoutData,
46
+ relationReorder,
47
+ relationDisconnect,
48
+ relationConnect,
49
+ isCreatingEntry
50
+ } = useCMEditViewDataManager() as any;
51
+ const { locales } = useSelector((state: any) => state.i18n_locales);
52
+ const urlLocale = useGetLocaleFromUrl();
53
+ const defaultLocale = locales.find((locale: any) => locale.isDefault);
54
+ const selectedLocale = initialData?.locale ?? urlLocale ?? defaultLocale.code;
55
+
56
+ const [searchResults, setSearchResults] = useState<Record<string, any>>({ data: [] });
57
+ const [selectedResults, setSelectedResults] = useState<Record<string, any>>({ data: [] });
58
+
59
+ const { formatMessage } = useIntl();
60
+
61
+ const { targetAttributes, targetFieldValue, targetField } = RelationHelper.getTargetAttributes(
62
+ name,
63
+ attribute,
64
+ modifiedData,
65
+ layout,
66
+ allLayoutData
67
+ );
68
+
69
+ const label = intlLabel?.id
70
+ ? formatMessage({ id: intlLabel.id, defaultMessage: intlLabel?.defaultMessage }, { ...intlLabel.values })
71
+ : name;
72
+
73
+ const hint = description?.id
74
+ ? formatMessage({ id: description.id, defaultMessage: description.defaultMessage }, { ...description.values })
75
+ : '';
76
+
77
+ useEffect(() => {
78
+ setSelectedResults({ data: targetFieldValue });
79
+ }, [targetFieldValue]);
80
+
81
+ const getItems = async (inputValue?: string) => {
82
+ const entities = await getSearchFilteredEntities({
83
+ fetchClient,
84
+ page: PAGE,
85
+ uid: targetAttributes?.target || '',
86
+ locale: selectedLocale,
87
+ searchQuery: inputValue,
88
+ platformTitle: initialData?.platform?.[0]?.title,
89
+ notIds: selectedResults?.data?.map((x) => x.id)
90
+ });
91
+
92
+ const mapped = entities?.results.map((x) => ({
93
+ id: x.id,
94
+ mainField: x.title,
95
+ href: x?.href,
96
+ publicationState: x.publicationState,
97
+ publishedAt: x.publishedAt
98
+ }));
99
+
100
+ setSearchResults({ data: mapped || [] });
101
+ };
102
+
103
+ const handleRelationReorder = (oldIndex: number, newIndex: number) => {
104
+ relationReorder?.({
105
+ name: targetField,
106
+ newIndex,
107
+ oldIndex
108
+ });
109
+ };
110
+
111
+ const handleRelationLoadMore = () => {
112
+ relations.fetchNextPage();
113
+ };
114
+
115
+ const handleRelationConnect = (relation: Record<string, any>) => {
116
+ relationConnect?.({ name: targetField, value: relation, toOneRelation: false });
117
+ };
118
+
119
+ const handleRelationDisconnect = (relation: Record<string, any>) => {
120
+ relationDisconnect?.({ name: targetField, id: relation.id });
121
+ };
122
+
123
+ return (
124
+ <RelationInput
125
+ id={name}
126
+ name={name}
127
+ error={error}
128
+ canReorder
129
+ description={hint}
130
+ iconButtonAriaLabel="Drag"
131
+ label={`${label} ${totalRelations > 0 ? `(${totalRelations})` : ''}`}
132
+ labelLoadMore={
133
+ !isCreatingEntry || isCloningEntry
134
+ ? formatMessage({
135
+ id: getTranslation('relation.loadMore'),
136
+ defaultMessage: 'Load More'
137
+ })
138
+ : undefined
139
+ }
140
+ liveText=""
141
+ listAriaDescription={formatMessage({
142
+ id: getTranslation('dnd.instructions'),
143
+ defaultMessage: `Press spacebar to grab and re-order`
144
+ })}
145
+ loadingMessage={formatMessage({
146
+ id: getTranslation('relation.isLoading'),
147
+ defaultMessage: 'Relations are loading'
148
+ })}
149
+ labelDisconnectRelation={formatMessage({
150
+ id: getTranslation('relation.disconnect'),
151
+ defaultMessage: 'Remove'
152
+ })}
153
+ numberOfRelationsToDisplay={5}
154
+ noRelationsMessage={formatMessage({
155
+ id: getTranslation('relation.notAvailable'),
156
+ defaultMessage: 'No relations available'
157
+ })}
158
+ onRelationConnect={handleRelationConnect}
159
+ onRelationDisconnect={handleRelationDisconnect}
160
+ onRelationLoadMore={handleRelationLoadMore}
161
+ onRelationReorder={handleRelationReorder}
162
+ onSearch={(e) => getItems(e)}
163
+ // onSearchNextPage={(e) => console.log(e)}
164
+ placeholder={formatMessage(
165
+ placeholder || {
166
+ id: getTranslation('relation.add'),
167
+ defaultMessage: 'Add relation'
168
+ }
169
+ )}
170
+ publicationStateTranslations={{
171
+ draft: 'Draft',
172
+ published: 'Published'
173
+ }}
174
+ relations={pick(
175
+ { ...relations, data: relationsFromModifiedData },
176
+ 'data',
177
+ 'hasNextPage',
178
+ 'isFetchingNextPage',
179
+ 'isLoading',
180
+ 'isSuccess'
181
+ )}
182
+ searchResults={searchResults}
183
+ size={8}
184
+ hint={hint}
185
+ labelAction={labelAction}
186
+ required={required}
187
+ />
188
+ );
189
+ };
190
+
191
+ export default MultiPlatformFilteredSelectField;
@@ -0,0 +1,184 @@
1
+ import React, { useEffect, useState } from 'react';
2
+ import { useIntl } from 'react-intl';
3
+ import debounce from 'lodash/debounce';
4
+ import { useCMEditViewDataManager } from '@strapi/helper-plugin';
5
+ import { useSelector } from 'react-redux';
6
+ import { Field } from '@strapi/design-system';
7
+ import { useFetchClient } from '@strapi/helper-plugin';
8
+ import { Link } from '@strapi/icons';
9
+
10
+ import { Combobox, IReactSelectValue } from '../../Combobox';
11
+ import { OptionProps, SingleValue, components } from 'react-select';
12
+ import { useGetLocaleFromUrl } from '../../../utils/hooks/useGetLocaleFromUrl';
13
+ import { getSearchFilteredEntities } from '../../../api/search-filtered-entity';
14
+ import S from './../styles';
15
+ import getTrad from '../../../utils/getTrad';
16
+ import RelationHelper, { IPlatformFilteredSelectFieldProps } from '../utils/relation-helper';
17
+
18
+ interface CustomReactSelectValue extends Omit<IReactSelectValue, 'initialSelected'> {
19
+ href?: string;
20
+ publicationState?: string;
21
+ publishedAt?: string;
22
+ }
23
+
24
+ const SEARCH_DEBOUNCE_MS = 150;
25
+ const PAGE = 1;
26
+
27
+ const SinglePlatformFilteredSelectField = ({
28
+ attribute,
29
+ description,
30
+ labelAction,
31
+ error,
32
+ intlLabel,
33
+ name
34
+ }: IPlatformFilteredSelectFieldProps) => {
35
+ const fetchClient = useFetchClient();
36
+ const { onChange, initialData, modifiedData, isCreatingEntry, layout, allLayoutData } =
37
+ useCMEditViewDataManager() as any;
38
+ const { locales } = useSelector((state: any) => state.i18n_locales);
39
+ const urlLocale = useGetLocaleFromUrl();
40
+ const defaultLocale = locales.find((locale: any) => locale.isDefault);
41
+ const selectedLocale = initialData?.locale ?? urlLocale ?? defaultLocale.code;
42
+
43
+ const [selected, setSelected] = useState<SingleValue<CustomReactSelectValue | null>>();
44
+
45
+ const { formatMessage } = useIntl();
46
+
47
+ const { targetAttributes, targetFieldValue, targetField } = RelationHelper.getTargetAttributes(
48
+ name,
49
+ attribute,
50
+ modifiedData,
51
+ layout,
52
+ allLayoutData
53
+ );
54
+
55
+ const label = intlLabel?.id
56
+ ? formatMessage({ id: intlLabel.id, defaultMessage: intlLabel?.defaultMessage }, { ...intlLabel.values })
57
+ : name;
58
+
59
+ const hint = description?.id
60
+ ? formatMessage({ id: description.id, defaultMessage: description.defaultMessage }, { ...description.values })
61
+ : '';
62
+
63
+ useEffect(() => {
64
+ const initialSelect = getInitialSelectItem(targetFieldValue?.[0]);
65
+
66
+ setSelected(initialSelect);
67
+ }, [targetFieldValue]);
68
+
69
+ useEffect(() => {
70
+ if (isCreatingEntry) {
71
+ setSelected(null);
72
+ }
73
+ }, []);
74
+
75
+ const setFormValue = (name: string, value?: string | Record<string, any>[]) => {
76
+ onChange({
77
+ target: {
78
+ name,
79
+ value
80
+ },
81
+ shouldSetInitialValue: true
82
+ });
83
+ };
84
+
85
+ const handleChange = (value?: SingleValue<CustomReactSelectValue>) => {
86
+ if (value) {
87
+ const newFormValue = {
88
+ ...value,
89
+ id: value.value,
90
+ title: value.label,
91
+ mainField: value.label,
92
+ label: value.label,
93
+ value: value.value
94
+ };
95
+ setFormValue(targetField, [newFormValue]);
96
+ } else {
97
+ setFormValue(targetField, []);
98
+ }
99
+ };
100
+
101
+ const getItems = async (inputValue?: string, platformTitle?: string): Promise<CustomReactSelectValue[]> => {
102
+ const pages = await getSearchFilteredEntities({
103
+ fetchClient,
104
+ page: PAGE,
105
+ uid: targetAttributes?.target || '',
106
+ locale: selectedLocale,
107
+ searchQuery: inputValue,
108
+ platformTitle
109
+ });
110
+
111
+ return pages?.results.map((x) => ({
112
+ value: String(x.id),
113
+ label: x.title,
114
+ href: x?.href,
115
+ publicationState: x.publicationState,
116
+ publishedAt: x.publishedAt
117
+ }));
118
+ };
119
+
120
+ const debouncedFetch = debounce((searchTerm, callback, selectedPlatformTitle?: string) => {
121
+ promiseOptions(searchTerm, selectedPlatformTitle).then((result) => {
122
+ return callback(result || []);
123
+ });
124
+ }, SEARCH_DEBOUNCE_MS);
125
+
126
+ const promiseOptions = (inputValue: string, selectedPlatformTitle?: string): Promise<CustomReactSelectValue[]> =>
127
+ new Promise<CustomReactSelectValue[]>((resolve) => {
128
+ resolve(getItems(inputValue, selectedPlatformTitle));
129
+ });
130
+
131
+ return (
132
+ <Field name={name} id={name} error={error} hint={hint}>
133
+ {targetAttributes?.type === 'relation' && (
134
+ <S.FieldWrapper>
135
+ <Combobox
136
+ key={`rerenderOnPlatformChange-${initialData?.platform?.[0]?.id}`}
137
+ id="pagesSearch"
138
+ label={label}
139
+ loadOptions={(i, c) => debouncedFetch(i, c, initialData?.platform?.[0]?.title)}
140
+ cacheOptions
141
+ customOption={CustomOption}
142
+ onChange={handleChange}
143
+ value={selected}
144
+ defaultOptions
145
+ hideSelectedOptions
146
+ labelAction={labelAction}
147
+ />
148
+
149
+ <S.LinkToPage
150
+ title={formatMessage({ id: getTrad('platformFilteredSelect.linkToEntity.label') })}
151
+ to={selected?.href}
152
+ disabled={!selected?.href}
153
+ >
154
+ <Link />
155
+ </S.LinkToPage>
156
+ </S.FieldWrapper>
157
+ )}
158
+ </Field>
159
+ );
160
+ };
161
+
162
+ const CustomOption = (props: OptionProps<CustomReactSelectValue, false>) => {
163
+ return (
164
+ <components.Option {...props}>
165
+ <S.CustomOption>
166
+ <S.CustomOptionStatus publicationState={props.data?.publicationState} />
167
+ {props.children}
168
+ </S.CustomOption>
169
+ </components.Option>
170
+ );
171
+ };
172
+
173
+ const getInitialSelectItem = (initialValue?: Record<string, any>): SingleValue<CustomReactSelectValue | null> =>
174
+ initialValue?.id
175
+ ? {
176
+ value: String(initialValue?.id),
177
+ label: initialValue?.title ?? '',
178
+ href: initialValue?.href,
179
+ publicationState: initialValue.publicationState,
180
+ publishedAt: initialValue.publishedAt
181
+ }
182
+ : null;
183
+
184
+ export default SinglePlatformFilteredSelectField;
@@ -0,0 +1,124 @@
1
+ import get from 'lodash/get';
2
+ import React from 'react';
3
+ import { useRelation } from '@strapi/admin/admin/src/content-manager/hooks/useRelation/index';
4
+ import { getInitialDataPathUsingTempKeys } from '@strapi/admin/admin/src/content-manager/utils/paths';
5
+ import RelationHelper, { IPlatformFilteredSelectFieldProps } from '../utils/relation-helper';
6
+ import { useCMEditViewDataManager } from '@strapi/helper-plugin';
7
+ import { useRouteMatch } from 'react-router-dom';
8
+
9
+ const RELATIONS_TO_DISPLAY = 5;
10
+ const SEARCH_RESULTS_TO_DISPLAY = 10;
11
+
12
+ // This is largely a copy of https://github.com/strapi/strapi/blob/4e6961c7b8127f0f3bb0ad1fc430351ae9c4b8fa/packages/core/admin/admin/src/content-manager/components/Relations/RelationInputDataManager.tsx
13
+
14
+ const useRelationLoad = ({ name, attribute }: IPlatformFilteredSelectFieldProps) => {
15
+ const fieldName = name || '';
16
+ const { modifiedData, initialData, slug, layout, allLayoutData, isCreatingEntry, relationLoad } =
17
+ useCMEditViewDataManager() as any;
18
+ const { params } =
19
+ useRouteMatch<{ origin?: string }>('/content-manager/collectionType/:collectionType/create/clone/:origin') ?? {};
20
+ const { origin } = params ?? {};
21
+ const isCloningEntry = Boolean(origin);
22
+ const entityId = origin || modifiedData.id;
23
+
24
+ const { componentUid, componentId, relationMainFieldName, targetAttributes, targetField } =
25
+ RelationHelper.getTargetAttributes(fieldName, attribute, modifiedData, layout, allLayoutData);
26
+
27
+ const nameSplit = targetField.split('.');
28
+ const isComponentRelation = Boolean(componentUid);
29
+ const initialDataPath = getInitialDataPathUsingTempKeys(initialData, modifiedData)(targetField);
30
+
31
+ const currentLastPage = Math.ceil((get(initialData, targetField, []) || []).length / RELATIONS_TO_DISPLAY);
32
+
33
+ // /content-manager/relations/[model]/[id]/[field-name]
34
+ const relationFetchEndpoint = React.useMemo(() => {
35
+ if (isCreatingEntry && !origin) {
36
+ return null;
37
+ }
38
+
39
+ if (componentUid) {
40
+ // repeatable components and dz are dynamically created
41
+ // if no componentId exists in modifiedData it means that the user just created it
42
+ // there then are no relations to request
43
+ return componentId ? `/content-manager/relations/${componentUid}/${componentId}/${nameSplit.at(-1)}` : null;
44
+ }
45
+
46
+ return `/content-manager/relations/${slug}/${entityId}/${targetField.split('.').at(-1)}`;
47
+ }, [isCreatingEntry, origin, componentUid, slug, entityId, targetField, componentId, nameSplit]);
48
+
49
+ // /content-manager/relations/[model]/[field-name]
50
+ const relationSearchEndpoint = React.useMemo(() => {
51
+ if (componentUid) {
52
+ return `/content-manager/relations/${componentUid}/${targetField.split('.').at(-1)}`;
53
+ }
54
+
55
+ return `/content-manager/relations/${slug}/${targetField.split('.').at(-1)}`;
56
+ }, [componentUid, slug, targetField]);
57
+
58
+ const { relations, search, searchFor } = useRelation([slug, initialDataPath.join('.'), modifiedData.id], {
59
+ relation: {
60
+ enabled: !!relationFetchEndpoint,
61
+ endpoint: relationFetchEndpoint!,
62
+ pageGoal: currentLastPage,
63
+ pageParams: {
64
+ pageSize: RELATIONS_TO_DISPLAY
65
+ },
66
+ onLoad(value) {
67
+ relationLoad?.({
68
+ target: {
69
+ initialDataPath: ['initialData', ...initialDataPath],
70
+ modifiedDataPath: ['modifiedData', ...nameSplit],
71
+ value
72
+ }
73
+ });
74
+ },
75
+ normalizeArguments: {
76
+ mainFieldName: relationMainFieldName,
77
+ shouldAddLink: true,
78
+ targetModel: targetAttributes?.targetModel
79
+ }
80
+ },
81
+ search: {
82
+ endpoint: relationSearchEndpoint,
83
+ pageParams: {
84
+ // eslint-disable-next-line no-nested-ternary
85
+ entityId: isCreatingEntry || isCloningEntry ? undefined : isComponentRelation ? componentId : entityId,
86
+ pageSize: SEARCH_RESULTS_TO_DISPLAY
87
+ }
88
+ }
89
+ });
90
+
91
+ /**
92
+ * How to calculate the total number of relations even if you don't
93
+ * have them all loaded in the browser.
94
+ *
95
+ * 1. The `infiniteQuery` gives you the total number of relations in the pagination result.
96
+ * 2. You can diff the length of the browserState vs the fetchedServerState to determine if you've
97
+ * either added or removed relations.
98
+ * 3. Add them together, if you've removed relations you'll get a negative number and it'll
99
+ * actually subtract from the total number on the server (regardless of how many you fetched).
100
+ */
101
+ const relationsFromModifiedData = get(modifiedData, targetField, []);
102
+ const browserRelationsCount = (relationsFromModifiedData || [])?.length;
103
+ const serverRelationsCount = (get(initialData, initialDataPath) ?? []).length;
104
+ const realServerRelationsCount = relations.data?.pages[0]?.pagination?.total ?? 0;
105
+ /**
106
+ * _IF_ theres no relations data and the browserCount is the same as serverCount you can therefore assume
107
+ * that the browser count is correct because we've just _made_ this entry and the in-component hook is now fetching.
108
+ */
109
+ const totalRelations =
110
+ !relations.data && browserRelationsCount === serverRelationsCount
111
+ ? browserRelationsCount
112
+ : browserRelationsCount - serverRelationsCount + realServerRelationsCount;
113
+
114
+ return {
115
+ relations,
116
+ search,
117
+ searchFor,
118
+ isCloningEntry,
119
+ relationsFromModifiedData,
120
+ totalRelations: totalRelations || 0
121
+ };
122
+ };
123
+
124
+ export { useRelationLoad };
@@ -0,0 +1,43 @@
1
+ import React from 'react';
2
+ import { useCMEditViewDataManager } from '@strapi/helper-plugin';
3
+
4
+ import RelationHelper, { IPlatformFilteredSelectFieldProps } from './utils/relation-helper';
5
+ import MultiPlatformFilteredSelectField from './Multi';
6
+ import SinglePlatformFilteredSelectField from './Single';
7
+ import { useRelationLoad } from './hooks/useRelationLoad';
8
+
9
+ const PlatformFilteredSelectField = (props: IPlatformFilteredSelectFieldProps) => {
10
+ const { name, attribute } = props;
11
+ const { modifiedData, layout, allLayoutData } = useCMEditViewDataManager() as any;
12
+ const { relations, isCloningEntry, totalRelations, relationsFromModifiedData } = useRelationLoad(props);
13
+ const { targetAttributes } = RelationHelper.getTargetAttributes(name, attribute, modifiedData, layout, allLayoutData);
14
+ const toOneRelation = ['oneWay', 'oneToOne', 'manyToOne', 'oneToManyMorph', 'oneToOneMorph'].includes(
15
+ targetAttributes?.relation || ''
16
+ );
17
+
18
+ if (toOneRelation) {
19
+ return <SinglePlatformFilteredSelectField {...props} />;
20
+ }
21
+
22
+ return (
23
+ <MultiPlatformFilteredSelectField
24
+ relations={relations}
25
+ isCloningEntry={isCloningEntry}
26
+ totalRelations={totalRelations}
27
+ relationsFromModifiedData={relationsFromModifiedData}
28
+ {...props}
29
+ />
30
+ );
31
+ };
32
+
33
+ PlatformFilteredSelectField.defaultProps = {
34
+ description: undefined,
35
+ disabled: false,
36
+ error: undefined,
37
+ labelAction: undefined,
38
+ placeholder: undefined,
39
+ value: '',
40
+ required: false
41
+ };
42
+
43
+ export default PlatformFilteredSelectField;