@firecms/core 3.0.0-canary.165 → 3.0.0-canary.167
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/common/default_entity_actions.d.ts +0 -2
- package/dist/components/common/useColumnsIds.d.ts +1 -0
- package/dist/index.es.js +39 -39
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +38 -38
- package/dist/index.umd.js.map +1 -1
- package/dist/types/collections.d.ts +1 -1
- package/dist/types/dialogs_controller.d.ts +6 -2
- package/dist/types/entity_actions.d.ts +15 -0
- package/dist/util/entity_actions.d.ts +2 -0
- package/dist/util/index.d.ts +1 -0
- package/package.json +5 -5
- package/src/components/EntityCollectionView/EntityCollectionView.tsx +5 -4
- package/src/components/common/default_entity_actions.tsx +3 -28
- package/src/components/common/useColumnsIds.tsx +1 -1
- package/src/contexts/DialogsProvider.tsx +1 -0
- package/src/core/EntityEditView.tsx +6 -2
- package/src/preview/PropertyPreview.tsx +2 -2
- package/src/types/collections.ts +1 -1
- package/src/types/dialogs_controller.tsx +6 -2
- package/src/types/entity_actions.tsx +18 -0
- package/src/util/entity_actions.ts +28 -0
- package/src/util/index.ts +1 -0
|
@@ -105,7 +105,7 @@ export interface EntityCollection<M extends Record<string, any> = any, USER exte
|
|
|
105
105
|
* `hidden` in the property definition,will be ignored.
|
|
106
106
|
* `propertiesOrder` has precedence over `hidden`.
|
|
107
107
|
*/
|
|
108
|
-
propertiesOrder?: (Extract<keyof M, string> | `subcollection:${string}`)[];
|
|
108
|
+
propertiesOrder?: (Extract<keyof M, string> | string | `subcollection:${string}` | "collectionGroupParent")[];
|
|
109
109
|
/**
|
|
110
110
|
* If enabled, content is loaded in batches. If `false` all entities in the
|
|
111
111
|
* collection are loaded.
|
|
@@ -20,7 +20,7 @@ export interface DialogsController {
|
|
|
20
20
|
* Props used to open a side dialog
|
|
21
21
|
* @group Hooks and utilities
|
|
22
22
|
*/
|
|
23
|
-
export interface DialogControllerEntryProps {
|
|
23
|
+
export interface DialogControllerEntryProps<T extends object = {}> {
|
|
24
24
|
key: string;
|
|
25
25
|
/**
|
|
26
26
|
* The component type that will be rendered
|
|
@@ -28,5 +28,9 @@ export interface DialogControllerEntryProps {
|
|
|
28
28
|
Component: React.ComponentType<{
|
|
29
29
|
open: boolean;
|
|
30
30
|
closeDialog: () => void;
|
|
31
|
-
}>;
|
|
31
|
+
} & T>;
|
|
32
|
+
/**
|
|
33
|
+
* Props to pass to the dialog component
|
|
34
|
+
*/
|
|
35
|
+
props?: T;
|
|
32
36
|
}
|
|
@@ -4,6 +4,7 @@ import { Entity } from "./entities";
|
|
|
4
4
|
import { EntityCollection, SelectionController } from "./collections";
|
|
5
5
|
import { User } from "./user";
|
|
6
6
|
import { SideEntityController } from "./side_entity_controller";
|
|
7
|
+
import { FormContext } from "./fields";
|
|
7
8
|
/**
|
|
8
9
|
* An entity action is a custom action that can be performed on an entity.
|
|
9
10
|
* They are displayed in the entity view and in the collection view.
|
|
@@ -13,6 +14,15 @@ export type EntityAction<M extends object = any, USER extends User = User> = {
|
|
|
13
14
|
* Title of the action
|
|
14
15
|
*/
|
|
15
16
|
name: string;
|
|
17
|
+
/**
|
|
18
|
+
* Key of the action. You only need to provide this if you want to
|
|
19
|
+
* override the default actions.
|
|
20
|
+
* The default actions are:
|
|
21
|
+
* - edit
|
|
22
|
+
* - delete
|
|
23
|
+
* - copy
|
|
24
|
+
*/
|
|
25
|
+
key?: string;
|
|
16
26
|
/**
|
|
17
27
|
* Icon of the action
|
|
18
28
|
*/
|
|
@@ -46,4 +56,9 @@ export type EntityActionClickProps<M extends object, USER extends User = User> =
|
|
|
46
56
|
* If this actions is being called from a side dialog
|
|
47
57
|
*/
|
|
48
58
|
sideEntityController?: SideEntityController;
|
|
59
|
+
/**
|
|
60
|
+
* You can use the form context to manage the state of the form.
|
|
61
|
+
* This is only available if this action in being triggered from a form context.
|
|
62
|
+
*/
|
|
63
|
+
formContext?: FormContext<M>;
|
|
49
64
|
};
|
package/dist/util/index.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ export * from "./objects";
|
|
|
7
7
|
export * from "./paths";
|
|
8
8
|
export * from "./regexp";
|
|
9
9
|
export * from "./navigation_utils";
|
|
10
|
+
export * from "./entity_actions";
|
|
10
11
|
export * from "./useDebouncedCallback";
|
|
11
12
|
export * from "./property_utils";
|
|
12
13
|
export * from "./resolutions";
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@firecms/core",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "3.0.0-canary.
|
|
4
|
+
"version": "3.0.0-canary.167",
|
|
5
5
|
"description": "Awesome Firebase/Firestore-based headless open-source CMS",
|
|
6
6
|
"funding": {
|
|
7
7
|
"url": "https://github.com/sponsors/firecmsco"
|
|
@@ -50,9 +50,9 @@
|
|
|
50
50
|
"./package.json": "./package.json"
|
|
51
51
|
},
|
|
52
52
|
"dependencies": {
|
|
53
|
-
"@firecms/editor": "^3.0.0-canary.
|
|
54
|
-
"@firecms/formex": "^3.0.0-canary.
|
|
55
|
-
"@firecms/ui": "^3.0.0-canary.
|
|
53
|
+
"@firecms/editor": "^3.0.0-canary.167",
|
|
54
|
+
"@firecms/formex": "^3.0.0-canary.167",
|
|
55
|
+
"@firecms/ui": "^3.0.0-canary.167",
|
|
56
56
|
"@hello-pangea/dnd": "^17.0.0",
|
|
57
57
|
"@radix-ui/react-portal": "^1.1.2",
|
|
58
58
|
"clsx": "^2.1.1",
|
|
@@ -106,7 +106,7 @@
|
|
|
106
106
|
"dist",
|
|
107
107
|
"src"
|
|
108
108
|
],
|
|
109
|
-
"gitHead": "
|
|
109
|
+
"gitHead": "86c9b7ff34991bf0424764845936b002e1071560",
|
|
110
110
|
"publishConfig": {
|
|
111
111
|
"access": "public"
|
|
112
112
|
},
|
|
@@ -61,6 +61,7 @@ import {
|
|
|
61
61
|
import { setIn } from "@firecms/formex";
|
|
62
62
|
import { getSubcollectionColumnId } from "../EntityCollectionTable/internal/common";
|
|
63
63
|
import {
|
|
64
|
+
COLLECTION_GROUP_PARENT_ID,
|
|
64
65
|
OnCellValueChange,
|
|
65
66
|
OnColumnResizeParams,
|
|
66
67
|
UniqueFieldValidator,
|
|
@@ -75,8 +76,7 @@ import { useAnalyticsController } from "../../hooks/useAnalyticsController";
|
|
|
75
76
|
import { useSelectionController } from "./useSelectionController";
|
|
76
77
|
import { EntityCollectionViewStartActions } from "./EntityCollectionViewStartActions";
|
|
77
78
|
import { addRecentId, getRecentIds } from "./utils";
|
|
78
|
-
|
|
79
|
-
const COLLECTION_GROUP_PARENT_ID = "collectionGroupParent";
|
|
79
|
+
import { mergeEntityActions } from "../../util/entity_actions";
|
|
80
80
|
|
|
81
81
|
/**
|
|
82
82
|
* @group Components
|
|
@@ -458,7 +458,7 @@ export const EntityCollectionView = React.memo(
|
|
|
458
458
|
if (deleteEnabled)
|
|
459
459
|
actions.push(deleteEntityAction);
|
|
460
460
|
if (customEntityActions)
|
|
461
|
-
actions
|
|
461
|
+
return mergeEntityActions(actions, customEntityActions);
|
|
462
462
|
return actions;
|
|
463
463
|
};
|
|
464
464
|
|
|
@@ -798,7 +798,8 @@ function EntityIdHeaderWidget({
|
|
|
798
798
|
<SearchIcon size={"small"}/>
|
|
799
799
|
</IconButton>
|
|
800
800
|
}>
|
|
801
|
-
<div
|
|
801
|
+
<div
|
|
802
|
+
className={cls("my-2 rounded-lg bg-surface-50 dark:bg-surface-950 text-surface-900 dark:text-white")}>
|
|
802
803
|
<form noValidate={true}
|
|
803
804
|
onSubmit={(e) => {
|
|
804
805
|
e.preventDefault();
|
|
@@ -5,6 +5,7 @@ import { addRecentId } from "../EntityCollectionView/utils";
|
|
|
5
5
|
|
|
6
6
|
export const editEntityAction: EntityAction = {
|
|
7
7
|
icon: <EditIcon/>,
|
|
8
|
+
key: "edit",
|
|
8
9
|
name: "Edit",
|
|
9
10
|
collapsed: false,
|
|
10
11
|
onClick({
|
|
@@ -38,6 +39,7 @@ export const editEntityAction: EntityAction = {
|
|
|
38
39
|
export const copyEntityAction: EntityAction = {
|
|
39
40
|
icon: <FileCopyIcon/>,
|
|
40
41
|
name: "Copy",
|
|
42
|
+
key: "copy",
|
|
41
43
|
onClick({
|
|
42
44
|
entity,
|
|
43
45
|
collection,
|
|
@@ -62,38 +64,11 @@ export const copyEntityAction: EntityAction = {
|
|
|
62
64
|
}
|
|
63
65
|
}
|
|
64
66
|
|
|
65
|
-
export const archiveEntityAction: EntityAction = {
|
|
66
|
-
icon: <ArchiveIcon/>,
|
|
67
|
-
name: "Archive",
|
|
68
|
-
onClick({
|
|
69
|
-
entity,
|
|
70
|
-
collection,
|
|
71
|
-
context: {
|
|
72
|
-
dataSource,
|
|
73
|
-
}
|
|
74
|
-
}): Promise<void> {
|
|
75
|
-
// Add your code here
|
|
76
|
-
return Promise.resolve(undefined);
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
export const openWebsiteAction: EntityAction = {
|
|
81
|
-
icon: <OpenInNewIcon/>,
|
|
82
|
-
name: "See in website",
|
|
83
|
-
onClick({
|
|
84
|
-
entity,
|
|
85
|
-
collection,
|
|
86
|
-
context,
|
|
87
|
-
}): Promise<void> {
|
|
88
|
-
// open a new tab
|
|
89
|
-
window.open(`https://example.com/${entity.id}`, "_blank");
|
|
90
|
-
return Promise.resolve(undefined);
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
67
|
|
|
94
68
|
export const deleteEntityAction: EntityAction = {
|
|
95
69
|
icon: <DeleteIcon/>,
|
|
96
70
|
name: "Delete",
|
|
71
|
+
key: "delete",
|
|
97
72
|
onClick({
|
|
98
73
|
entity,
|
|
99
74
|
fullPath,
|
|
@@ -3,7 +3,7 @@ import { EntityCollection, ResolvedEntityCollection, ResolvedProperty } from "..
|
|
|
3
3
|
import { getSubcollectionColumnId } from "../EntityCollectionTable/internal/common";
|
|
4
4
|
import { PropertyColumnConfig } from "../EntityCollectionTable/EntityCollectionTableProps";
|
|
5
5
|
|
|
6
|
-
const COLLECTION_GROUP_PARENT_ID = "collectionGroupParent";
|
|
6
|
+
export const COLLECTION_GROUP_PARENT_ID = "collectionGroupParent";
|
|
7
7
|
|
|
8
8
|
export function useColumnIds<M extends Record<string, any>>(collection: ResolvedEntityCollection<M>, includeSubcollections: boolean): PropertyColumnConfig[] {
|
|
9
9
|
return useMemo(() => {
|
|
@@ -34,6 +34,7 @@ import {
|
|
|
34
34
|
getValueInPath,
|
|
35
35
|
isHidden,
|
|
36
36
|
isReadOnly,
|
|
37
|
+
mergeEntityActions,
|
|
37
38
|
removeInitialAndTrailingSlashes,
|
|
38
39
|
resolveCollection,
|
|
39
40
|
resolveDefaultSelectedView,
|
|
@@ -702,7 +703,7 @@ export function EntityEditViewInner<M extends Record<string, any>>({
|
|
|
702
703
|
if (deleteEnabled)
|
|
703
704
|
actions.push(deleteEntityAction);
|
|
704
705
|
if (customEntityActions)
|
|
705
|
-
actions
|
|
706
|
+
return mergeEntityActions(actions, customEntityActions);
|
|
706
707
|
return actions;
|
|
707
708
|
}, [authController, inputCollection, path]);
|
|
708
709
|
|
|
@@ -841,14 +842,17 @@ export function EntityEditViewInner<M extends Record<string, any>>({
|
|
|
841
842
|
fullPath: resolvedCollection.path,
|
|
842
843
|
collection: resolvedCollection,
|
|
843
844
|
context,
|
|
844
|
-
sideEntityController
|
|
845
|
+
sideEntityController,
|
|
846
|
+
formContext
|
|
845
847
|
});
|
|
846
848
|
}}>
|
|
847
849
|
{action.icon}
|
|
848
850
|
</IconButton>
|
|
849
851
|
))}
|
|
850
852
|
</div>}
|
|
853
|
+
|
|
851
854
|
{formex.isSubmitting && <CircularProgress size={"small"}/>}
|
|
855
|
+
|
|
852
856
|
<Button
|
|
853
857
|
variant="text"
|
|
854
858
|
disabled={disabled || formex.isSubmitting}
|
|
@@ -56,7 +56,7 @@ export const PropertyPreview = React.memo(function PropertyPreview<T extends CMS
|
|
|
56
56
|
propertyConfigs: customizationController.propertyConfigs
|
|
57
57
|
});
|
|
58
58
|
|
|
59
|
-
if (
|
|
59
|
+
if (property === null) {
|
|
60
60
|
content = <EmptyValue/>;
|
|
61
61
|
} else if (property.Preview) {
|
|
62
62
|
content = createElement(property.Preview as React.ComponentType<PropertyPreviewProps>,
|
|
@@ -70,7 +70,7 @@ export const PropertyPreview = React.memo(function PropertyPreview<T extends CMS
|
|
|
70
70
|
// entity,
|
|
71
71
|
customProps: property.customProps
|
|
72
72
|
});
|
|
73
|
-
} else if (value === null) {
|
|
73
|
+
} else if (value === undefined || value === null) {
|
|
74
74
|
content = <EmptyValue/>;
|
|
75
75
|
} else if (property.dataType === "string") {
|
|
76
76
|
const stringProperty = property as ResolvedStringProperty;
|
package/src/types/collections.ts
CHANGED
|
@@ -119,7 +119,7 @@ export interface EntityCollection<M extends Record<string, any> = any, USER exte
|
|
|
119
119
|
* `hidden` in the property definition,will be ignored.
|
|
120
120
|
* `propertiesOrder` has precedence over `hidden`.
|
|
121
121
|
*/
|
|
122
|
-
propertiesOrder?: (Extract<keyof M, string> | `subcollection:${string}`)[];
|
|
122
|
+
propertiesOrder?: (Extract<keyof M, string> | string | `subcollection:${string}` | "collectionGroupParent")[];
|
|
123
123
|
|
|
124
124
|
/**
|
|
125
125
|
* If enabled, content is loaded in batches. If `false` all entities in the
|
|
@@ -22,12 +22,16 @@ export interface DialogsController {
|
|
|
22
22
|
* Props used to open a side dialog
|
|
23
23
|
* @group Hooks and utilities
|
|
24
24
|
*/
|
|
25
|
-
export interface DialogControllerEntryProps {
|
|
25
|
+
export interface DialogControllerEntryProps<T extends object = {}> {
|
|
26
26
|
|
|
27
27
|
key: string;
|
|
28
28
|
/**
|
|
29
29
|
* The component type that will be rendered
|
|
30
30
|
*/
|
|
31
|
-
Component: React.ComponentType<{ open: boolean, closeDialog: () => void }>;
|
|
31
|
+
Component: React.ComponentType<{ open: boolean, closeDialog: () => void } & T>;
|
|
32
|
+
/**
|
|
33
|
+
* Props to pass to the dialog component
|
|
34
|
+
*/
|
|
35
|
+
props?: T;
|
|
32
36
|
|
|
33
37
|
}
|
|
@@ -4,6 +4,8 @@ import { Entity } from "./entities";
|
|
|
4
4
|
import { EntityCollection, SelectionController } from "./collections";
|
|
5
5
|
import { User } from "./user";
|
|
6
6
|
import { SideEntityController } from "./side_entity_controller";
|
|
7
|
+
import { FormexController } from "@firecms/formex";
|
|
8
|
+
import { FormContext } from "./fields";
|
|
7
9
|
|
|
8
10
|
/**
|
|
9
11
|
* An entity action is a custom action that can be performed on an entity.
|
|
@@ -15,6 +17,16 @@ export type EntityAction<M extends object = any, USER extends User = User> = {
|
|
|
15
17
|
*/
|
|
16
18
|
name: string;
|
|
17
19
|
|
|
20
|
+
/**
|
|
21
|
+
* Key of the action. You only need to provide this if you want to
|
|
22
|
+
* override the default actions.
|
|
23
|
+
* The default actions are:
|
|
24
|
+
* - edit
|
|
25
|
+
* - delete
|
|
26
|
+
* - copy
|
|
27
|
+
*/
|
|
28
|
+
key?: string;
|
|
29
|
+
|
|
18
30
|
/**
|
|
19
31
|
* Icon of the action
|
|
20
32
|
*/
|
|
@@ -53,4 +65,10 @@ export type EntityActionClickProps<M extends object, USER extends User = User> =
|
|
|
53
65
|
* If this actions is being called from a side dialog
|
|
54
66
|
*/
|
|
55
67
|
sideEntityController?: SideEntityController;
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* You can use the form context to manage the state of the form.
|
|
71
|
+
* This is only available if this action in being triggered from a form context.
|
|
72
|
+
*/
|
|
73
|
+
formContext?: FormContext<M>;
|
|
56
74
|
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { EntityAction } from "../types";
|
|
2
|
+
|
|
3
|
+
const reservedKeys = ["edit", "copy", "delete"];
|
|
4
|
+
|
|
5
|
+
export function mergeEntityActions(currentActions: EntityAction[], newActions: EntityAction[]): EntityAction[] {
|
|
6
|
+
// given the current actions, replace the ones with the same key
|
|
7
|
+
// and append the new ones
|
|
8
|
+
const updatedActions: EntityAction[] = [];
|
|
9
|
+
currentActions.forEach(action => {
|
|
10
|
+
const newAction = newActions.find(a => a.key === action.key);
|
|
11
|
+
if (newAction) {
|
|
12
|
+
const mergedAction = {
|
|
13
|
+
...action,
|
|
14
|
+
...newAction
|
|
15
|
+
}
|
|
16
|
+
updatedActions.push(mergedAction);
|
|
17
|
+
} else {
|
|
18
|
+
updatedActions.push(action);
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
newActions.forEach(action => {
|
|
22
|
+
if (!currentActions.find(a => a.key === action.key) && (!action.key || !reservedKeys.includes(action.key))) {
|
|
23
|
+
updatedActions.push(action);
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
return updatedActions;
|
|
27
|
+
|
|
28
|
+
}
|
package/src/util/index.ts
CHANGED
|
@@ -7,6 +7,7 @@ export * from "./objects";
|
|
|
7
7
|
export * from "./paths";
|
|
8
8
|
export * from "./regexp";
|
|
9
9
|
export * from "./navigation_utils";
|
|
10
|
+
export * from "./entity_actions";
|
|
10
11
|
export * from "./useDebouncedCallback";
|
|
11
12
|
export * from "./property_utils";
|
|
12
13
|
export * from "./resolutions";
|