@firecms/core 3.0.0-canary.235 → 3.0.0-canary.239
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.
- package/dist/components/EntityPreview.d.ts +4 -2
- package/dist/components/SelectableTable/SelectableTable.d.ts +1 -1
- package/dist/components/index.d.ts +1 -0
- package/dist/hooks/useBuildNavigationController.d.ts +2 -9
- package/dist/index.es.js +271 -144
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +271 -144
- package/dist/index.umd.js.map +1 -1
- package/dist/types/collections.d.ts +13 -0
- package/dist/types/plugins.d.ts +12 -0
- package/dist/types/side_entity_controller.d.ts +4 -0
- package/dist/util/callbacks.d.ts +2 -0
- package/dist/util/index.d.ts +1 -0
- package/package.json +5 -5
- package/src/components/ConfirmationDialog.tsx +9 -9
- package/src/components/EntityCollectionTable/PropertyTableCell.tsx +1 -1
- package/src/components/EntityCollectionTable/internal/CollectionTableToolbar.tsx +1 -1
- package/src/components/EntityCollectionView/EntityCollectionView.tsx +2 -0
- package/src/components/EntityPreview.tsx +18 -14
- package/src/components/ErrorView.tsx +1 -1
- package/src/components/HomePage/DefaultHomePage.tsx +1 -1
- package/src/components/SelectableTable/SelectableTable.tsx +140 -143
- package/src/components/VirtualTable/VirtualTable.tsx +7 -4
- package/src/components/index.tsx +2 -0
- package/src/core/EntityEditView.tsx +24 -14
- package/src/core/EntitySidePanel.tsx +17 -10
- package/src/form/components/LabelWithIcon.tsx +1 -1
- package/src/form/field_bindings/KeyValueFieldBinding.tsx +0 -2
- package/src/form/field_bindings/MarkdownEditorFieldBinding.tsx +1 -1
- package/src/form/field_bindings/MultiSelectFieldBinding.tsx +1 -1
- package/src/form/field_bindings/ReadOnlyFieldBinding.tsx +1 -1
- package/src/form/field_bindings/ReferenceFieldBinding.tsx +1 -1
- package/src/form/field_bindings/SelectFieldBinding.tsx +1 -1
- package/src/hooks/useBuildNavigationController.tsx +29 -16
- package/src/internal/useBuildSideEntityController.tsx +1 -1
- package/src/preview/components/ReferencePreview.tsx +1 -1
- package/src/preview/property_previews/ArrayOfMapsPreview.tsx +1 -1
- package/src/preview/property_previews/SkeletonPropertyComponent.tsx +1 -1
- package/src/types/collections.ts +16 -0
- package/src/types/firecms.tsx +0 -1
- package/src/types/plugins.tsx +16 -0
- package/src/types/side_entity_controller.tsx +5 -5
- package/src/util/callbacks.ts +119 -0
- package/src/util/index.ts +1 -0
|
@@ -182,11 +182,13 @@ export function EntityEditViewInner<M extends Record<string, any>>({
|
|
|
182
182
|
|
|
183
183
|
const subcollections = (collection.subcollections ?? []).filter(c => !c.hideFromNavigation);
|
|
184
184
|
const subcollectionsCount = subcollections?.length ?? 0;
|
|
185
|
-
const customViews = collection.entityViews;
|
|
185
|
+
const customViews = collection.entityViews ?? [];
|
|
186
186
|
const customViewsCount = customViews?.length ?? 0;
|
|
187
187
|
const includeJsonView = collection.includeJsonView === undefined ? true : collection.includeJsonView;
|
|
188
188
|
const hasAdditionalViews = customViewsCount > 0 || subcollectionsCount > 0 || includeJsonView;
|
|
189
189
|
|
|
190
|
+
const plugins = customizationController.plugins;
|
|
191
|
+
|
|
190
192
|
const {
|
|
191
193
|
resolvedEntityViews,
|
|
192
194
|
selectedEntityView,
|
|
@@ -212,7 +214,6 @@ export function EntityEditViewInner<M extends Record<string, any>>({
|
|
|
212
214
|
}
|
|
213
215
|
|
|
214
216
|
if (!entityId) {
|
|
215
|
-
console.error("INTERNAL: entityId is not defined");
|
|
216
217
|
return null;
|
|
217
218
|
}
|
|
218
219
|
|
|
@@ -384,14 +385,24 @@ export function EntityEditViewInner<M extends Record<string, any>>({
|
|
|
384
385
|
</Tab>
|
|
385
386
|
);
|
|
386
387
|
|
|
387
|
-
const
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
388
|
+
const customViewTabsStart = resolvedEntityViews.filter(view => view.position === "start")
|
|
389
|
+
.map((view) =>
|
|
390
|
+
<Tab
|
|
391
|
+
className={!view.tabComponent ? "text-sm min-w-[120px]" : undefined}
|
|
392
|
+
value={view.key}
|
|
393
|
+
key={`entity_detail_collection_tab_${view.name}`}>
|
|
394
|
+
{view.tabComponent ?? view.name}
|
|
395
|
+
</Tab>
|
|
396
|
+
);
|
|
397
|
+
const customViewTabsEnd = resolvedEntityViews.filter(view => !view.position || view.position === "end")
|
|
398
|
+
.map((view) =>
|
|
399
|
+
<Tab
|
|
400
|
+
className={!view.tabComponent ? "text-sm min-w-[120px]" : undefined}
|
|
401
|
+
value={view.key}
|
|
402
|
+
key={`entity_detail_collection_tab_${view.name}`}>
|
|
403
|
+
{view.tabComponent ?? view.name}
|
|
404
|
+
</Tab>
|
|
405
|
+
);
|
|
395
406
|
|
|
396
407
|
const shouldShowTopBar = Boolean(barActions) || hasAdditionalViews;
|
|
397
408
|
|
|
@@ -417,11 +428,12 @@ export function EntityEditViewInner<M extends Record<string, any>>({
|
|
|
417
428
|
{includeJsonView && <Tab
|
|
418
429
|
disabled={!hasAdditionalViews}
|
|
419
430
|
value={JSON_TAB_VALUE}
|
|
420
|
-
innerClassName={"block"}
|
|
421
431
|
className={"text-sm"}>
|
|
422
432
|
<CodeIcon size={"small"}/>
|
|
423
433
|
</Tab>}
|
|
424
434
|
|
|
435
|
+
{customViewTabsStart}
|
|
436
|
+
|
|
425
437
|
<Tab
|
|
426
438
|
disabled={!hasAdditionalViews}
|
|
427
439
|
value={MAIN_TAB_VALUE}
|
|
@@ -430,7 +442,7 @@ export function EntityEditViewInner<M extends Record<string, any>>({
|
|
|
430
442
|
</Tab>
|
|
431
443
|
|
|
432
444
|
|
|
433
|
-
{
|
|
445
|
+
{customViewTabsEnd}
|
|
434
446
|
|
|
435
447
|
{subcollectionTabs}
|
|
436
448
|
</Tabs>}
|
|
@@ -453,8 +465,6 @@ export function EntityEditViewInner<M extends Record<string, any>>({
|
|
|
453
465
|
|
|
454
466
|
</div>;
|
|
455
467
|
|
|
456
|
-
const plugins = customizationController.plugins;
|
|
457
|
-
|
|
458
468
|
if (plugins) {
|
|
459
469
|
plugins.forEach((plugin: FireCMSPlugin) => {
|
|
460
470
|
if (plugin.form?.provider) {
|
|
@@ -19,11 +19,18 @@ import { useLocation, useNavigate } from "react-router-dom";
|
|
|
19
19
|
*/
|
|
20
20
|
export function EntitySidePanel(props: EntitySidePanelProps) {
|
|
21
21
|
|
|
22
|
+
const {
|
|
23
|
+
allowFullScreen = true,
|
|
24
|
+
path,
|
|
25
|
+
entityId,
|
|
26
|
+
formProps,
|
|
27
|
+
} = props;
|
|
28
|
+
|
|
22
29
|
const {
|
|
23
30
|
blocked,
|
|
24
31
|
setBlocked,
|
|
25
32
|
setBlockedNavigationMessage,
|
|
26
|
-
close
|
|
33
|
+
close,
|
|
27
34
|
} = useSideDialogContext();
|
|
28
35
|
|
|
29
36
|
const navigate = useNavigate();
|
|
@@ -64,21 +71,21 @@ export function EntitySidePanel(props: EntitySidePanelProps) {
|
|
|
64
71
|
}
|
|
65
72
|
|
|
66
73
|
const parentCollectionIds = useMemo(() => {
|
|
67
|
-
return navigationController.getParentCollectionIds(
|
|
68
|
-
}, [navigationController,
|
|
74
|
+
return navigationController.getParentCollectionIds(path);
|
|
75
|
+
}, [navigationController, path]);
|
|
69
76
|
|
|
70
77
|
const collection = useMemo(() => {
|
|
71
78
|
if (props.collection) {
|
|
72
79
|
return props.collection;
|
|
73
80
|
}
|
|
74
81
|
|
|
75
|
-
const registryCollection = navigationController.getCollection(
|
|
82
|
+
const registryCollection = navigationController.getCollection(path);
|
|
76
83
|
if (registryCollection) {
|
|
77
84
|
return registryCollection;
|
|
78
85
|
}
|
|
79
86
|
|
|
80
|
-
console.error("ERROR: No collection found in path `",
|
|
81
|
-
throw Error("ERROR: No collection found in path `" +
|
|
87
|
+
console.error("ERROR: No collection found in path `", path, "`. Entity id: ", entityId);
|
|
88
|
+
throw Error("ERROR: No collection found in path `" + path + "`. Make sure you have defined a collection for this path in the root navigation.");
|
|
82
89
|
}, [navigationController, props.collection]);
|
|
83
90
|
|
|
84
91
|
useEffect(() => {
|
|
@@ -126,16 +133,16 @@ export function EntitySidePanel(props: EntitySidePanelProps) {
|
|
|
126
133
|
onClick={onClose}>
|
|
127
134
|
<CloseIcon size={"small"}/>
|
|
128
135
|
</IconButton>
|
|
129
|
-
<IconButton
|
|
136
|
+
{allowFullScreen && <IconButton
|
|
130
137
|
className="self-center"
|
|
131
138
|
onClick={() => {
|
|
132
|
-
if (
|
|
139
|
+
if (entityId)
|
|
133
140
|
navigate(location.pathname);
|
|
134
141
|
else
|
|
135
142
|
navigate(location.pathname + "#new");
|
|
136
143
|
}}>
|
|
137
144
|
<OpenInFullIcon size={"small"}/>
|
|
138
|
-
</IconButton>
|
|
145
|
+
</IconButton>}
|
|
139
146
|
</>}
|
|
140
147
|
onTabChange={({
|
|
141
148
|
path,
|
|
@@ -151,7 +158,7 @@ export function EntitySidePanel(props: EntitySidePanelProps) {
|
|
|
151
158
|
collection,
|
|
152
159
|
});
|
|
153
160
|
}}
|
|
154
|
-
formProps={
|
|
161
|
+
formProps={formProps}
|
|
155
162
|
/>
|
|
156
163
|
</ErrorBoundary>
|
|
157
164
|
|
|
@@ -24,7 +24,7 @@ export const LabelWithIcon = forwardRef<HTMLDivElement, LabelWithIconProps>(
|
|
|
24
24
|
return (
|
|
25
25
|
<div
|
|
26
26
|
ref={ref}
|
|
27
|
-
className={cls("inline-flex items-center my-0.5",
|
|
27
|
+
className={cls("align-middle inline-flex items-center my-0.5",
|
|
28
28
|
small ? "gap-1" : "gap-2",
|
|
29
29
|
className)}
|
|
30
30
|
>
|
|
@@ -141,7 +141,7 @@ export function MarkdownEditorFieldBinding({
|
|
|
141
141
|
icon={getIconForProperty(property, "small")}
|
|
142
142
|
required={property.validation?.required}
|
|
143
143
|
title={property.name}
|
|
144
|
-
className={"h-
|
|
144
|
+
className={"h-6 text-text-secondary dark:text-text-secondary-dark ml-3.5"}/>
|
|
145
145
|
<div
|
|
146
146
|
className={cls("rounded-md", fieldBackgroundMixin, disabled ? fieldBackgroundDisabledMixin : fieldBackgroundHoverMixin)}>
|
|
147
147
|
{editor}
|
|
@@ -92,7 +92,7 @@ export function MultiSelectFieldBinding({
|
|
|
92
92
|
icon={getIconForProperty(property, "small")}
|
|
93
93
|
required={property.validation?.required}
|
|
94
94
|
title={property.name}
|
|
95
|
-
className={"h-
|
|
95
|
+
className={"h-6 text-text-secondary dark:text-text-secondary-dark ml-3.5"}/>}
|
|
96
96
|
onValueChange={(updatedValue: string[]) => {
|
|
97
97
|
let newValue: EnumType[] | null;
|
|
98
98
|
if (of && (of as ResolvedProperty)?.dataType === "number") {
|
|
@@ -39,7 +39,7 @@ export function ReadOnlyFieldBinding({
|
|
|
39
39
|
icon={getIconForProperty(property, "small")}
|
|
40
40
|
required={property.validation?.required}
|
|
41
41
|
title={property.name}
|
|
42
|
-
className={"h-
|
|
42
|
+
className={"h-6 text-text-secondary dark:text-text-secondary-dark ml-3.5"}/>
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
<div
|
|
@@ -90,7 +90,7 @@ function ReferenceFieldBindingInternal({
|
|
|
90
90
|
icon={getIconForProperty(property, "small")}
|
|
91
91
|
required={property.validation?.required}
|
|
92
92
|
title={property.name}
|
|
93
|
-
className={"h-
|
|
93
|
+
className={"h-6 text-text-secondary dark:text-text-secondary-dark ml-3.5"}/>}
|
|
94
94
|
|
|
95
95
|
{!collection && <ErrorView
|
|
96
96
|
error={"The specified collection does not exist. Check console"}/>}
|
|
@@ -62,7 +62,7 @@ export function SelectFieldBinding<T extends EnumType>({
|
|
|
62
62
|
icon={getIconForProperty(property, "small")}
|
|
63
63
|
required={property.validation?.required}
|
|
64
64
|
title={property.name}
|
|
65
|
-
className={"h-
|
|
65
|
+
className={"h-6 text-text-secondary dark:text-text-secondary-dark ml-3.5 my-0"}
|
|
66
66
|
/>
|
|
67
67
|
</PropertyIdCopyTooltip>}
|
|
68
68
|
endAdornment={
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
EntityCollection,
|
|
10
10
|
EntityCollectionsBuilder,
|
|
11
11
|
EntityReference,
|
|
12
|
+
FireCMSPlugin,
|
|
12
13
|
NavigationBlocker,
|
|
13
14
|
NavigationController,
|
|
14
15
|
PermissionsBuilder,
|
|
@@ -43,15 +44,7 @@ export type BuildNavigationContextProps<EC extends EntityCollection, USER extend
|
|
|
43
44
|
viewsOrder?: string[];
|
|
44
45
|
userConfigPersistence?: UserConfigurationPersistence;
|
|
45
46
|
dataSourceDelegate: DataSourceDelegate;
|
|
46
|
-
|
|
47
|
-
* Use this method to inject collections to the CMS.
|
|
48
|
-
* You receive the current collections as a parameter, and you can return
|
|
49
|
-
* a new list of collections.
|
|
50
|
-
* @see {@link joinCollectionLists}
|
|
51
|
-
* @param collections
|
|
52
|
-
*/
|
|
53
|
-
injectCollections?: (collections: EntityCollection[]) => EntityCollection[];
|
|
54
|
-
|
|
47
|
+
plugins?: FireCMSPlugin[];
|
|
55
48
|
/**
|
|
56
49
|
* If true, the navigation logic will not be updated until this flag is false
|
|
57
50
|
*/
|
|
@@ -68,9 +61,9 @@ export function useBuildNavigationController<EC extends EntityCollection, USER e
|
|
|
68
61
|
views: viewsProp,
|
|
69
62
|
adminViews: adminViewsProp,
|
|
70
63
|
viewsOrder,
|
|
64
|
+
plugins,
|
|
71
65
|
userConfigPersistence,
|
|
72
66
|
dataSourceDelegate,
|
|
73
|
-
injectCollections,
|
|
74
67
|
disabled
|
|
75
68
|
} = props;
|
|
76
69
|
|
|
@@ -203,7 +196,7 @@ export function useBuildNavigationController<EC extends EntityCollection, USER e
|
|
|
203
196
|
try {
|
|
204
197
|
|
|
205
198
|
const [resolvedCollections = [], resolvedViews, resolvedAdminViews = []] = await Promise.all([
|
|
206
|
-
resolveCollections(collectionsProp, collectionPermissions, authController, dataSourceDelegate,
|
|
199
|
+
resolveCollections(collectionsProp, collectionPermissions, authController, dataSourceDelegate, plugins),
|
|
207
200
|
resolveCMSViews(viewsProp, authController, dataSourceDelegate),
|
|
208
201
|
resolveCMSViews(adminViewsProp, authController, dataSourceDelegate)
|
|
209
202
|
]
|
|
@@ -250,8 +243,7 @@ export function useBuildNavigationController<EC extends EntityCollection, USER e
|
|
|
250
243
|
disabled,
|
|
251
244
|
viewsProp,
|
|
252
245
|
adminViewsProp,
|
|
253
|
-
computeTopNavigation
|
|
254
|
-
injectCollections
|
|
246
|
+
computeTopNavigation
|
|
255
247
|
]);
|
|
256
248
|
|
|
257
249
|
useEffect(() => {
|
|
@@ -447,11 +439,24 @@ function filterOutNotAllowedCollections(resolvedCollections: EntityCollection[],
|
|
|
447
439
|
});
|
|
448
440
|
}
|
|
449
441
|
|
|
442
|
+
function applyPluginModifyCollection(resolvedCollections: EntityCollection[], modifyCollection: (collection: EntityCollection) => EntityCollection) {
|
|
443
|
+
return resolvedCollections.map((collection: EntityCollection): EntityCollection => {
|
|
444
|
+
const modifiedCollection = modifyCollection(collection);
|
|
445
|
+
if (modifiedCollection.subcollections) {
|
|
446
|
+
return {
|
|
447
|
+
...modifiedCollection,
|
|
448
|
+
subcollections: applyPluginModifyCollection(modifiedCollection.subcollections, modifyCollection)
|
|
449
|
+
} satisfies EntityCollection;
|
|
450
|
+
}
|
|
451
|
+
return modifiedCollection;
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
|
|
450
455
|
async function resolveCollections(collections: undefined | EntityCollection[] | EntityCollectionsBuilder<any>,
|
|
451
456
|
collectionPermissions: PermissionsBuilder | undefined,
|
|
452
457
|
authController: AuthController,
|
|
453
458
|
dataSource: DataSourceDelegate,
|
|
454
|
-
|
|
459
|
+
plugins: FireCMSPlugin[] | undefined): Promise<EntityCollection[]> {
|
|
455
460
|
let resolvedCollections: EntityCollection[] = [];
|
|
456
461
|
if (typeof collections === "function") {
|
|
457
462
|
resolvedCollections = await collections({
|
|
@@ -463,8 +468,16 @@ async function resolveCollections(collections: undefined | EntityCollection[] |
|
|
|
463
468
|
resolvedCollections = collections;
|
|
464
469
|
}
|
|
465
470
|
|
|
466
|
-
if (
|
|
467
|
-
|
|
471
|
+
if (plugins) {
|
|
472
|
+
for (const plugin of plugins) {
|
|
473
|
+
if (plugin.collection?.modifyCollection) {
|
|
474
|
+
resolvedCollections = applyPluginModifyCollection(resolvedCollections, plugin.collection.modifyCollection);
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
if (plugin.collection?.injectCollections) {
|
|
478
|
+
resolvedCollections = plugin.collection.injectCollections(resolvedCollections ?? []);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
468
481
|
}
|
|
469
482
|
|
|
470
483
|
resolvedCollections = applyPermissionsFunctionIfEmpty(resolvedCollections, collectionPermissions);
|
|
@@ -33,7 +33,7 @@ export function getEntityViewWidth(props: EntitySidePanelProps<any>, small: bool
|
|
|
33
33
|
selectedSecondaryForm
|
|
34
34
|
} = resolvedSelectedEntityView(props.collection?.entityViews, customizationController, props.selectedTab);
|
|
35
35
|
|
|
36
|
-
const shouldUseSmallLayout = !props.selectedTab || props.selectedTab === JSON_TAB_VALUE || Boolean(selectedSecondaryForm);
|
|
36
|
+
const shouldUseSmallLayout = !props.selectedTab || props.selectedTab === JSON_TAB_VALUE || props.selectedTab === "__history" || Boolean(selectedSecondaryForm);
|
|
37
37
|
|
|
38
38
|
let resolvedWidth: string | undefined;
|
|
39
39
|
if (props.width) {
|
|
@@ -141,7 +141,7 @@ function ReferencePreviewExisting<M extends Record<string, any> = any>({
|
|
|
141
141
|
);
|
|
142
142
|
}
|
|
143
143
|
return <EntityPreview size={size}
|
|
144
|
-
|
|
144
|
+
previewKeys={previewProperties}
|
|
145
145
|
disabled={disabled}
|
|
146
146
|
entity={usedEntity}
|
|
147
147
|
collection={collection}
|
|
@@ -37,7 +37,7 @@ export function ArrayOfMapsPreview({
|
|
|
37
37
|
throw Error(`You need to specify a 'properties' prop (or specify a custom field) in your map property ${propertyKey}`);
|
|
38
38
|
}
|
|
39
39
|
const values = value;
|
|
40
|
-
const previewProperties: string[] | undefined = mapProperty.
|
|
40
|
+
const previewProperties: string[] | undefined = mapProperty.previewKeys;
|
|
41
41
|
|
|
42
42
|
if (!values) return null;
|
|
43
43
|
|
|
@@ -46,7 +46,7 @@ export function SkeletonPropertyComponent({
|
|
|
46
46
|
content = <>{arrayProperty.of.map((p, i) => renderGenericArrayCell(p, i))} </>;
|
|
47
47
|
} else {
|
|
48
48
|
if (arrayProperty.of.dataType === "map" && arrayProperty.of.properties) {
|
|
49
|
-
content = renderArrayOfMaps(arrayProperty.of.properties, size, arrayProperty.of.
|
|
49
|
+
content = renderArrayOfMaps(arrayProperty.of.properties, size, arrayProperty.of.previewKeys);
|
|
50
50
|
} else if (arrayProperty.of.dataType === "string") {
|
|
51
51
|
if (arrayProperty.of.enumValues) {
|
|
52
52
|
content = renderArrayEnumTableCell();
|
package/src/types/collections.ts
CHANGED
|
@@ -337,6 +337,12 @@ export interface EntityCollection<M extends Record<string, any> = any, USER exte
|
|
|
337
337
|
* If set to true, a tab including the JSON representation of the entity will be included.
|
|
338
338
|
*/
|
|
339
339
|
includeJsonView?: boolean;
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* If set to true, changes to the entity will be saved in a subcollection.
|
|
343
|
+
* This prop has no effect if the history plugin is not enabled
|
|
344
|
+
*/
|
|
345
|
+
history?: boolean;
|
|
340
346
|
}
|
|
341
347
|
|
|
342
348
|
/**
|
|
@@ -519,6 +525,11 @@ export type EntityCustomView<M extends Record<string, any> = any> =
|
|
|
519
525
|
*/
|
|
520
526
|
name: string;
|
|
521
527
|
|
|
528
|
+
/**
|
|
529
|
+
* Render this custom view in the tab of the entity view, instead of the name
|
|
530
|
+
*/
|
|
531
|
+
tabComponent?: React.ReactNode;
|
|
532
|
+
|
|
522
533
|
/**
|
|
523
534
|
* If set to true, the actions of the entity (save, discard,delete) will be
|
|
524
535
|
* included in the view. By default the actions are located in the right or bottom,
|
|
@@ -531,6 +542,11 @@ export type EntityCustomView<M extends Record<string, any> = any> =
|
|
|
531
542
|
* Builder for rendering the custom view
|
|
532
543
|
*/
|
|
533
544
|
Builder?: React.ComponentType<EntityCustomViewParams<M>>;
|
|
545
|
+
|
|
546
|
+
/**
|
|
547
|
+
* Position of this tab in the entity view. Defaults to `end`.
|
|
548
|
+
*/
|
|
549
|
+
position?: "start" | "end";
|
|
534
550
|
};
|
|
535
551
|
|
|
536
552
|
/**
|
package/src/types/firecms.tsx
CHANGED
package/src/types/plugins.tsx
CHANGED
|
@@ -158,6 +158,22 @@ export type FireCMSPlugin<PROPS = any, FORM_PROPS = any, EC extends EntityCollec
|
|
|
158
158
|
fieldBuilderEnabled?: <T extends CMSType = CMSType>(props: PluginFieldBuilderParams<T>) => boolean;
|
|
159
159
|
}
|
|
160
160
|
|
|
161
|
+
collection?: {
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Use this method to modify a single collection before it is rendered.
|
|
165
|
+
* @param collection
|
|
166
|
+
*/
|
|
167
|
+
modifyCollection?: (collection: EntityCollection) => EntityCollection;
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Use this method to modify, add or remove collections.
|
|
171
|
+
* @param collections
|
|
172
|
+
*/
|
|
173
|
+
injectCollections?: (collections: EntityCollection[]) => EntityCollection[];
|
|
174
|
+
|
|
175
|
+
}
|
|
176
|
+
|
|
161
177
|
}
|
|
162
178
|
|
|
163
179
|
/**
|
|
@@ -20,11 +20,6 @@ export interface EntitySidePanelProps<M extends Record<string, any> = any> {
|
|
|
20
20
|
*/
|
|
21
21
|
entityId?: string;
|
|
22
22
|
|
|
23
|
-
// /**
|
|
24
|
-
// * Navigation path with ids of the entity
|
|
25
|
-
// */
|
|
26
|
-
// fullIdPath: string;
|
|
27
|
-
|
|
28
23
|
/**
|
|
29
24
|
* Set this flag to true if you want to make a copy of an existing entity
|
|
30
25
|
*/
|
|
@@ -76,6 +71,11 @@ export interface EntitySidePanelProps<M extends Record<string, any> = any> {
|
|
|
76
71
|
* Override some form properties
|
|
77
72
|
*/
|
|
78
73
|
formProps?: Partial<EntityFormProps<M>>;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Allow the user to open the entity fullscreen
|
|
77
|
+
*/
|
|
78
|
+
allowFullScreen?: boolean;
|
|
79
79
|
}
|
|
80
80
|
|
|
81
81
|
/**
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { EntityCallbacks } from "../types";
|
|
2
|
+
|
|
3
|
+
export const mergeCallbacks = (
|
|
4
|
+
baseCallbacks: EntityCallbacks = {},
|
|
5
|
+
pluginCallbacks: EntityCallbacks = {}
|
|
6
|
+
): EntityCallbacks | undefined => {
|
|
7
|
+
|
|
8
|
+
if (!baseCallbacks && !pluginCallbacks) {
|
|
9
|
+
return undefined;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const mergedCallbacks: EntityCallbacks = {};
|
|
13
|
+
|
|
14
|
+
// Handle onFetch - returns Entity<M> or Promise<Entity<M>>
|
|
15
|
+
if (baseCallbacks.onFetch || pluginCallbacks.onFetch) {
|
|
16
|
+
mergedCallbacks.onFetch = async (props) => {
|
|
17
|
+
let entity = props.entity;
|
|
18
|
+
if (baseCallbacks.onFetch) {
|
|
19
|
+
entity = await Promise.resolve(baseCallbacks.onFetch(props));
|
|
20
|
+
}
|
|
21
|
+
if (pluginCallbacks.onFetch) {
|
|
22
|
+
entity = await Promise.resolve(pluginCallbacks.onFetch({
|
|
23
|
+
...props,
|
|
24
|
+
entity
|
|
25
|
+
}));
|
|
26
|
+
}
|
|
27
|
+
return entity;
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Handle onSaveSuccess - returns void or Promise<void>
|
|
32
|
+
if (baseCallbacks.onSaveSuccess || pluginCallbacks.onSaveSuccess) {
|
|
33
|
+
mergedCallbacks.onSaveSuccess = async (props) => {
|
|
34
|
+
if (baseCallbacks.onSaveSuccess) {
|
|
35
|
+
await Promise.resolve(baseCallbacks.onSaveSuccess(props));
|
|
36
|
+
}
|
|
37
|
+
if (pluginCallbacks.onSaveSuccess) {
|
|
38
|
+
await Promise.resolve(pluginCallbacks.onSaveSuccess(props));
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Handle onSaveFailure - returns void or Promise<void>
|
|
44
|
+
if (baseCallbacks.onSaveFailure || pluginCallbacks.onSaveFailure) {
|
|
45
|
+
mergedCallbacks.onSaveFailure = async (props) => {
|
|
46
|
+
if (baseCallbacks.onSaveFailure) {
|
|
47
|
+
await Promise.resolve(baseCallbacks.onSaveFailure(props));
|
|
48
|
+
}
|
|
49
|
+
if (pluginCallbacks.onSaveFailure) {
|
|
50
|
+
await Promise.resolve(pluginCallbacks.onSaveFailure(props));
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Handle onPreSave - returns Partial<EntityValues<M>> or Promise<Partial<EntityValues<M>>>
|
|
56
|
+
if (baseCallbacks.onPreSave || pluginCallbacks.onPreSave) {
|
|
57
|
+
mergedCallbacks.onPreSave = async (props) => {
|
|
58
|
+
let values = { ...props.values };
|
|
59
|
+
if (baseCallbacks.onPreSave) {
|
|
60
|
+
const baseValues = await Promise.resolve(baseCallbacks.onPreSave(props));
|
|
61
|
+
values = { ...values, ...baseValues };
|
|
62
|
+
}
|
|
63
|
+
if (pluginCallbacks.onPreSave) {
|
|
64
|
+
const pluginValues = await Promise.resolve(pluginCallbacks.onPreSave({
|
|
65
|
+
...props,
|
|
66
|
+
values
|
|
67
|
+
}));
|
|
68
|
+
values = { ...values, ...pluginValues };
|
|
69
|
+
}
|
|
70
|
+
return values;
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Handle onPreDelete - returns void
|
|
75
|
+
if (baseCallbacks.onPreDelete || pluginCallbacks.onPreDelete) {
|
|
76
|
+
mergedCallbacks.onPreDelete = (props) => {
|
|
77
|
+
if (baseCallbacks.onPreDelete) {
|
|
78
|
+
baseCallbacks.onPreDelete(props);
|
|
79
|
+
}
|
|
80
|
+
if (pluginCallbacks.onPreDelete) {
|
|
81
|
+
pluginCallbacks.onPreDelete(props);
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Handle onDelete - returns void
|
|
87
|
+
if (baseCallbacks.onDelete || pluginCallbacks.onDelete) {
|
|
88
|
+
mergedCallbacks.onDelete = (props) => {
|
|
89
|
+
if (baseCallbacks.onDelete) {
|
|
90
|
+
baseCallbacks.onDelete(props);
|
|
91
|
+
}
|
|
92
|
+
if (pluginCallbacks.onDelete) {
|
|
93
|
+
pluginCallbacks.onDelete(props);
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Handle onIdUpdate - returns string or Promise<string>
|
|
99
|
+
if (baseCallbacks.onIdUpdate || pluginCallbacks.onIdUpdate) {
|
|
100
|
+
mergedCallbacks.onIdUpdate = async (props) => {
|
|
101
|
+
let id = props.entityId || "";
|
|
102
|
+
|
|
103
|
+
if (baseCallbacks.onIdUpdate) {
|
|
104
|
+
id = await Promise.resolve(baseCallbacks.onIdUpdate(props));
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (pluginCallbacks.onIdUpdate) {
|
|
108
|
+
id = await Promise.resolve(pluginCallbacks.onIdUpdate({
|
|
109
|
+
...props,
|
|
110
|
+
entityId: id
|
|
111
|
+
}));
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return id;
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return Object.keys(mergedCallbacks).length > 0 ? mergedCallbacks : undefined;
|
|
119
|
+
};
|
package/src/util/index.ts
CHANGED