@firecms/core 3.0.0-canary.257 → 3.0.0-canary.259
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/core/EntityEditViewFormActions.d.ts +1 -1
- package/dist/form/EntityFormActions.d.ts +4 -2
- package/dist/index.es.js +340 -156
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +340 -156
- package/dist/index.umd.js.map +1 -1
- package/dist/types/collections.d.ts +4 -1
- package/dist/types/customization_controller.d.ts +8 -0
- package/dist/types/entity_actions.d.ts +45 -6
- package/dist/types/firecms.d.ts +8 -0
- package/dist/types/plugins.d.ts +8 -1
- package/dist/util/resolutions.d.ts +2 -1
- package/package.json +5 -5
- package/src/components/ConfirmationDialog.tsx +1 -0
- package/src/components/EntityCollectionTable/EntityCollectionRowActions.tsx +2 -0
- package/src/components/EntityCollectionTable/internal/CollectionTableToolbar.tsx +2 -2
- package/src/components/EntityCollectionView/EntityCollectionView.tsx +5 -2
- package/src/components/EntityCollectionView/EntityCollectionViewActions.tsx +3 -2
- package/src/components/HomePage/DefaultHomePage.tsx +48 -33
- package/src/components/HomePage/HomePageDnD.tsx +22 -36
- package/src/components/HomePage/RenameGroupDialog.tsx +6 -2
- package/src/components/UnsavedChangesDialog.tsx +6 -2
- package/src/components/common/default_entity_actions.tsx +18 -5
- package/src/components/common/useDataSourceTableController.tsx +1 -1
- package/src/core/EntityEditView.tsx +35 -12
- package/src/core/EntityEditViewFormActions.tsx +154 -29
- package/src/core/EntitySidePanel.tsx +1 -1
- package/src/core/FireCMS.tsx +2 -0
- package/src/form/EntityForm.tsx +32 -6
- package/src/form/EntityFormActions.tsx +37 -8
- package/src/form/field_bindings/MarkdownEditorFieldBinding.tsx +4 -2
- package/src/form/field_bindings/StorageUploadFieldBinding.tsx +1 -1
- package/src/types/collections.ts +4 -1
- package/src/types/customization_controller.tsx +9 -0
- package/src/types/entity_actions.tsx +56 -6
- package/src/types/firecms.tsx +9 -0
- package/src/types/plugins.tsx +9 -1
- package/src/util/join_collections.ts +3 -1
- package/src/util/resolutions.ts +13 -1
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
Entity,
|
|
4
|
+
EntityAction,
|
|
5
|
+
FireCMSContext,
|
|
6
|
+
FormContext,
|
|
7
|
+
ResolvedEntityCollection,
|
|
8
|
+
SideEntityController
|
|
9
|
+
} from "../types";
|
|
3
10
|
import { Button, cls, defaultBorderMixin, DialogActions, IconButton, LoadingButton, Typography } from "@firecms/ui";
|
|
4
11
|
import { FormexController } from "@firecms/formex";
|
|
5
12
|
import { useFireCMSContext, useSideEntityController } from "../hooks";
|
|
@@ -18,6 +25,8 @@ export interface EntityFormActionsProps {
|
|
|
18
25
|
pluginActions: React.ReactNode[];
|
|
19
26
|
openEntityMode: "side_panel" | "full_screen";
|
|
20
27
|
showDefaultActions?: boolean;
|
|
28
|
+
navigateBack: () => void;
|
|
29
|
+
formContext: FormContext
|
|
21
30
|
}
|
|
22
31
|
|
|
23
32
|
export function EntityFormActions({
|
|
@@ -31,7 +40,9 @@ export function EntityFormActions({
|
|
|
31
40
|
disabled,
|
|
32
41
|
status,
|
|
33
42
|
pluginActions,
|
|
34
|
-
openEntityMode
|
|
43
|
+
openEntityMode,
|
|
44
|
+
navigateBack,
|
|
45
|
+
formContext
|
|
35
46
|
}: EntityFormActionsProps) {
|
|
36
47
|
|
|
37
48
|
const context = useFireCMSContext();
|
|
@@ -50,7 +61,9 @@ export function EntityFormActions({
|
|
|
50
61
|
disabled,
|
|
51
62
|
status,
|
|
52
63
|
pluginActions,
|
|
53
|
-
openEntityMode
|
|
64
|
+
openEntityMode,
|
|
65
|
+
navigateBack,
|
|
66
|
+
formContext
|
|
54
67
|
})
|
|
55
68
|
: buildSideActions({
|
|
56
69
|
fullPath,
|
|
@@ -64,7 +77,9 @@ export function EntityFormActions({
|
|
|
64
77
|
disabled,
|
|
65
78
|
status,
|
|
66
79
|
pluginActions,
|
|
67
|
-
openEntityMode
|
|
80
|
+
openEntityMode,
|
|
81
|
+
navigateBack,
|
|
82
|
+
formContext
|
|
68
83
|
});
|
|
69
84
|
}
|
|
70
85
|
|
|
@@ -82,6 +97,8 @@ type ActionsViewProps<M extends object> = {
|
|
|
82
97
|
status: "new" | "existing" | "copy",
|
|
83
98
|
pluginActions?: React.ReactNode[],
|
|
84
99
|
openEntityMode: "side_panel" | "full_screen";
|
|
100
|
+
navigateBack: () => void;
|
|
101
|
+
formContext: FormContext
|
|
85
102
|
};
|
|
86
103
|
|
|
87
104
|
function buildBottomActions<M extends object>({
|
|
@@ -97,7 +114,9 @@ function buildBottomActions<M extends object>({
|
|
|
97
114
|
disabled,
|
|
98
115
|
status,
|
|
99
116
|
pluginActions,
|
|
100
|
-
openEntityMode
|
|
117
|
+
openEntityMode,
|
|
118
|
+
navigateBack,
|
|
119
|
+
formContext
|
|
101
120
|
}: ActionsViewProps<M>) {
|
|
102
121
|
|
|
103
122
|
return <DialogActions position={"absolute"}>
|
|
@@ -115,13 +134,16 @@ function buildBottomActions<M extends object>({
|
|
|
115
134
|
event.stopPropagation();
|
|
116
135
|
if (entity)
|
|
117
136
|
action.onClick({
|
|
137
|
+
view: "form",
|
|
118
138
|
entity,
|
|
119
139
|
fullPath: fullPath ?? collection.path,
|
|
120
140
|
fullIdPath: fullIdPath ?? collection.id,
|
|
121
141
|
collection: collection,
|
|
122
142
|
context,
|
|
123
143
|
sideEntityController,
|
|
124
|
-
openEntityMode: openEntityMode
|
|
144
|
+
openEntityMode: openEntityMode,
|
|
145
|
+
navigateBack,
|
|
146
|
+
formContext
|
|
125
147
|
});
|
|
126
148
|
}}>
|
|
127
149
|
{action.icon}
|
|
@@ -129,10 +151,14 @@ function buildBottomActions<M extends object>({
|
|
|
129
151
|
))}
|
|
130
152
|
</div>}
|
|
131
153
|
{pluginActions}
|
|
132
|
-
<Button variant="text" disabled={disabled || isSubmitting}
|
|
154
|
+
<Button variant="text" disabled={disabled || isSubmitting}
|
|
155
|
+
color={"primary"}
|
|
156
|
+
type="reset">
|
|
133
157
|
{status === "existing" ? "Discard" : "Clear"}
|
|
134
158
|
</Button>
|
|
135
|
-
<Button variant={"filled"}
|
|
159
|
+
<Button variant={"filled"}
|
|
160
|
+
color="primary"
|
|
161
|
+
type="submit"
|
|
136
162
|
disabled={disabled || isSubmitting}>
|
|
137
163
|
{status === "existing" && "Save"}
|
|
138
164
|
{status === "copy" && "Create copy"}
|
|
@@ -146,6 +172,9 @@ function buildSideActions<M extends object>({
|
|
|
146
172
|
savingError,
|
|
147
173
|
entity,
|
|
148
174
|
formActions,
|
|
175
|
+
fullPath,
|
|
176
|
+
fullIdPath,
|
|
177
|
+
openEntityMode,
|
|
149
178
|
collection,
|
|
150
179
|
context,
|
|
151
180
|
sideEntityController,
|
|
@@ -46,11 +46,13 @@ export function MarkdownEditorFieldBinding({
|
|
|
46
46
|
const entityId = context.entityId;
|
|
47
47
|
const path = context.path;
|
|
48
48
|
|
|
49
|
-
// const fieldVersion = useRef(0);
|
|
50
49
|
const [fieldVersion, setFieldVersion] = useState(0);
|
|
51
|
-
const internalValue = useRef(value);
|
|
50
|
+
const internalValue = useRef<string | null>(value);
|
|
52
51
|
|
|
53
52
|
const onContentChange = useCallback((content: string) => {
|
|
53
|
+
if (content === value || (value === null && content === "")) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
54
56
|
internalValue.current = content;
|
|
55
57
|
setValue(content);
|
|
56
58
|
}, [setValue]);
|
|
@@ -114,7 +114,7 @@ export function StorageUploadFieldBinding({
|
|
|
114
114
|
icon={getIconForProperty(property, "small")}
|
|
115
115
|
required={property.validation?.required}
|
|
116
116
|
title={property.name}
|
|
117
|
-
className={"h-
|
|
117
|
+
className={"h-8 text-text-secondary dark:text-text-secondary-dark ml-3.5"}/>}
|
|
118
118
|
|
|
119
119
|
<StorageUpload
|
|
120
120
|
value={internalValue}
|
package/src/types/collections.ts
CHANGED
|
@@ -196,8 +196,11 @@ export interface EntityCollection<M extends Record<string, any> = any, USER exte
|
|
|
196
196
|
* }
|
|
197
197
|
* }
|
|
198
198
|
* ```
|
|
199
|
+
*
|
|
200
|
+
* You can also pass the action as a string that represents the `key`, in which case it will
|
|
201
|
+
* use the action defined in the main configuration under `entityActions`.
|
|
199
202
|
*/
|
|
200
|
-
entityActions?: EntityAction<M, USER>[];
|
|
203
|
+
entityActions?: (EntityAction<M, USER> | string)[];
|
|
201
204
|
|
|
202
205
|
/**
|
|
203
206
|
* Pass your own selection controller if you want to control selected
|
|
@@ -4,6 +4,7 @@ import { FireCMSPlugin } from "./plugins";
|
|
|
4
4
|
import { EntityCustomView } from "./collections";
|
|
5
5
|
import { Locale } from "./locales";
|
|
6
6
|
import { PropertyConfig } from "./property_config";
|
|
7
|
+
import { EntityAction } from "./entity_actions";
|
|
7
8
|
|
|
8
9
|
export type CustomizationController = {
|
|
9
10
|
|
|
@@ -26,6 +27,14 @@ export type CustomizationController = {
|
|
|
26
27
|
*/
|
|
27
28
|
entityViews?: EntityCustomView[];
|
|
28
29
|
|
|
30
|
+
/**
|
|
31
|
+
* List of actions that can be performed on entities.
|
|
32
|
+
* These actions are displayed in the entity view and in the collection view.
|
|
33
|
+
* You can later reuse these actions in the `entityActions` prop of a collection,
|
|
34
|
+
* by specifying the `key` of the action.
|
|
35
|
+
*/
|
|
36
|
+
entityActions?: EntityAction<any, any>[];
|
|
37
|
+
|
|
29
38
|
/**
|
|
30
39
|
* Format of the dates in the CMS.
|
|
31
40
|
* Defaults to 'MMMM dd, yyyy, HH:mm:ss'
|
|
@@ -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
|
/**
|
|
9
10
|
* An entity action is a custom action that can be performed on an entity.
|
|
@@ -17,7 +18,10 @@ export type EntityAction<M extends object = any, USER extends User = User> = {
|
|
|
17
18
|
|
|
18
19
|
/**
|
|
19
20
|
* Key of the action. You only need to provide this if you want to
|
|
20
|
-
* override the default actions
|
|
21
|
+
* override the default actions, or if you are not passing the action
|
|
22
|
+
* directly to the `entityActions` prop of a collection.
|
|
23
|
+
* You can define your actions at the app level, in which case you
|
|
24
|
+
* must provide a key.
|
|
21
25
|
* The default actions are:
|
|
22
26
|
* - edit
|
|
23
27
|
* - delete
|
|
@@ -36,6 +40,12 @@ export type EntityAction<M extends object = any, USER extends User = User> = {
|
|
|
36
40
|
*/
|
|
37
41
|
onClick: (props: EntityActionClickProps<M, USER>) => Promise<void> | void;
|
|
38
42
|
|
|
43
|
+
/**
|
|
44
|
+
* Optional callback in case you want to disable the action
|
|
45
|
+
* @param props
|
|
46
|
+
*/
|
|
47
|
+
isEnabled?: (props: EntityActionClickProps<M, USER>) => boolean;
|
|
48
|
+
|
|
39
49
|
/**
|
|
40
50
|
* Show this action collapsed in the menu of the collection view.
|
|
41
51
|
* Defaults to true
|
|
@@ -51,19 +61,59 @@ export type EntityAction<M extends object = any, USER extends User = User> = {
|
|
|
51
61
|
}
|
|
52
62
|
|
|
53
63
|
export type EntityActionClickProps<M extends object, USER extends User = User> = {
|
|
54
|
-
entity
|
|
64
|
+
entity?: Entity<M>;
|
|
55
65
|
context: FireCMSContext<USER>;
|
|
66
|
+
|
|
56
67
|
fullPath?: string;
|
|
57
68
|
fullIdPath?: string;
|
|
58
69
|
collection?: EntityCollection<M>;
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Optional form context, present if the action is being called from a form.
|
|
73
|
+
* This allows you to access the form state and methods, including modifying the form values.
|
|
74
|
+
*/
|
|
75
|
+
formContext?: FormContext;
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Present if this actions is being called from a side dialog only
|
|
79
|
+
*/
|
|
80
|
+
sideEntityController?: SideEntityController;
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Is the action being called from the collection view or from the entity form view?
|
|
84
|
+
*/
|
|
85
|
+
view: "collection" | "form";
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* If the action is rendered in the form, is it open in a side panel or full screen?
|
|
89
|
+
*/
|
|
90
|
+
openEntityMode: "side_panel" | "full_screen";
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Optional selection controller, present if the action is being called from a collection view
|
|
94
|
+
*/
|
|
59
95
|
selectionController?: SelectionController;
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Optional highlight function to highlight the entity in the collection view
|
|
99
|
+
* @param entity
|
|
100
|
+
*/
|
|
60
101
|
highlightEntity?: (entity: Entity<any>) => void;
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Optional unhighlight function to remove the highlight from the entity in the collection view
|
|
105
|
+
* @param entity
|
|
106
|
+
*/
|
|
61
107
|
unhighlightEntity?: (entity: Entity<any>) => void;
|
|
62
|
-
|
|
108
|
+
|
|
63
109
|
/**
|
|
64
|
-
*
|
|
110
|
+
* Optional function to navigate back (e.g. when deleting an entity or navigating from a form)
|
|
65
111
|
*/
|
|
66
|
-
|
|
112
|
+
navigateBack?: () => void;
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Callback to be called when the collection changes, e.g. after an entity is deleted or created.
|
|
116
|
+
*/
|
|
117
|
+
onCollectionChange?: () => void;
|
|
67
118
|
|
|
68
|
-
openEntityMode: "side_panel" | "full_screen";
|
|
69
119
|
};
|
package/src/types/firecms.tsx
CHANGED
|
@@ -12,6 +12,7 @@ import { EntityLinkBuilder } from "./entity_link_builder";
|
|
|
12
12
|
import { UserConfigurationPersistence } from "./local_config_persistence";
|
|
13
13
|
import { FireCMSPlugin } from "./plugins";
|
|
14
14
|
import { CMSAnalyticsEvent } from "./analytics";
|
|
15
|
+
import { EntityAction } from "./entity_actions";
|
|
15
16
|
|
|
16
17
|
/**
|
|
17
18
|
* Use this callback to build entity collections dynamically.
|
|
@@ -94,6 +95,14 @@ export type FireCMSProps<USER extends User> = {
|
|
|
94
95
|
*/
|
|
95
96
|
entityViews?: EntityCustomView[];
|
|
96
97
|
|
|
98
|
+
/**
|
|
99
|
+
* List of actions that can be performed on entities.
|
|
100
|
+
* These actions are displayed in the entity view and in the collection view.
|
|
101
|
+
* You can later reuse these actions in the `entityActions` prop of a collection,
|
|
102
|
+
* by specifying the `key` of the action.
|
|
103
|
+
*/
|
|
104
|
+
entityActions?: EntityAction[];
|
|
105
|
+
|
|
97
106
|
/**
|
|
98
107
|
* Format of the dates in the CMS.
|
|
99
108
|
* Defaults to 'MMMM dd, yyyy, HH:mm:ss'
|
package/src/types/plugins.tsx
CHANGED
|
@@ -166,8 +166,16 @@ export type FireCMSPlugin<PROPS = any, FORM_PROPS = any, EC extends EntityCollec
|
|
|
166
166
|
props?: FORM_PROPS;
|
|
167
167
|
}
|
|
168
168
|
|
|
169
|
+
/**
|
|
170
|
+
* Add custom actions to the default ones ("Save", "Discard"...)
|
|
171
|
+
*/
|
|
169
172
|
Actions?: React.ComponentType<PluginFormActionProps<any, EC>>;
|
|
170
173
|
|
|
174
|
+
/**
|
|
175
|
+
* Add custom actions to the top of the form
|
|
176
|
+
*/
|
|
177
|
+
ActionsTop?: React.ComponentType<PluginFormActionProps<any, EC>>;
|
|
178
|
+
|
|
171
179
|
fieldBuilder?: <T extends CMSType = CMSType>(props: PluginFieldBuilderParams<T, any, EC>) => React.ComponentType<FieldProps<T>> | null;
|
|
172
180
|
|
|
173
181
|
fieldBuilderEnabled?: <T extends CMSType = CMSType>(props: PluginFieldBuilderParams<T>) => boolean;
|
|
@@ -221,12 +229,12 @@ export interface PluginHomePageActionsProps<EP extends object = object, M extend
|
|
|
221
229
|
export interface PluginFormActionProps<USER extends User = User, EC extends EntityCollection = EntityCollection> {
|
|
222
230
|
entityId?: string;
|
|
223
231
|
path: string;
|
|
232
|
+
parentCollectionIds: string[];
|
|
224
233
|
status: EntityStatus;
|
|
225
234
|
collection: EC;
|
|
226
235
|
disabled: boolean;
|
|
227
236
|
formContext?: FormContext<any>;
|
|
228
237
|
context: FireCMSContext<USER>;
|
|
229
|
-
currentEntityId?: string;
|
|
230
238
|
openEntityMode: "side_panel" | "full_screen";
|
|
231
239
|
}
|
|
232
240
|
|
|
@@ -100,13 +100,15 @@ export function mergeCollection(target: EntityCollection,
|
|
|
100
100
|
const sourcePropertiesOrder = getCollectionKeys(source);
|
|
101
101
|
const mergedPropertiesOrder = [...new Set([...sourcePropertiesOrder, ...targetPropertiesOrder])];
|
|
102
102
|
const mergedEntityViews = [...new Set([...(target.entityViews ?? []), ...(source.entityViews ?? [])])];
|
|
103
|
+
const mergedEntityActions = [...new Set([...(target.entityActions ?? []), ...(source.entityActions ?? [])])];
|
|
103
104
|
|
|
104
105
|
let resultCollection: EntityCollection = {
|
|
105
106
|
...mergedCollection,
|
|
106
107
|
subcollections: subcollectionsMerged,
|
|
107
108
|
properties: sortProperties(propertiesMerged, mergedPropertiesOrder),
|
|
108
109
|
propertiesOrder: mergedPropertiesOrder,
|
|
109
|
-
entityViews: mergedEntityViews
|
|
110
|
+
entityViews: mergedEntityViews,
|
|
111
|
+
entityActions: mergedEntityActions,
|
|
110
112
|
};
|
|
111
113
|
if (modifyCollection) {
|
|
112
114
|
const modifiedCollection = modifyCollection({
|
package/src/util/resolutions.ts
CHANGED
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
AuthController,
|
|
4
4
|
CMSType,
|
|
5
5
|
CustomizationController,
|
|
6
|
+
EntityAction,
|
|
6
7
|
EntityCollection,
|
|
7
8
|
EntityCustomView,
|
|
8
9
|
EntityValues,
|
|
@@ -438,6 +439,17 @@ export function resolveEntityView(entityView: string | EntityCustomView<any>, co
|
|
|
438
439
|
}
|
|
439
440
|
}
|
|
440
441
|
|
|
442
|
+
export function resolveEntityAction<M extends Record<string, any>>(
|
|
443
|
+
entityAction: string | EntityAction<M>,
|
|
444
|
+
contextEntityActions?: EntityAction<M>[]
|
|
445
|
+
): EntityAction<M> | undefined {
|
|
446
|
+
if (typeof entityAction === "string") {
|
|
447
|
+
return contextEntityActions?.find((entry) => entry.key === entityAction);
|
|
448
|
+
} else {
|
|
449
|
+
return entityAction;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
441
453
|
export function resolvedSelectedEntityView<M extends Record<string, any>>(
|
|
442
454
|
customViews: (string | EntityCustomView<M>)[] | undefined,
|
|
443
455
|
customizationController: CustomizationController,
|
|
@@ -447,7 +459,7 @@ export function resolvedSelectedEntityView<M extends Record<string, any>>(
|
|
|
447
459
|
const resolvedEntityViews = customViews ? customViews
|
|
448
460
|
.map(e => resolveEntityView(e, customizationController.entityViews))
|
|
449
461
|
.filter((e): e is EntityCustomView<M> => Boolean(e))
|
|
450
|
-
|
|
462
|
+
// .filter((e) => canEdit || !e.includeActions)
|
|
451
463
|
: [];
|
|
452
464
|
|
|
453
465
|
const selectedEntityView = resolvedEntityViews.find(e => e.key === selectedTab);
|