@firecms/core 3.0.0-canary.258 → 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.
Files changed (39) hide show
  1. package/dist/core/EntityEditViewFormActions.d.ts +1 -1
  2. package/dist/form/EntityFormActions.d.ts +4 -2
  3. package/dist/index.es.js +339 -155
  4. package/dist/index.es.js.map +1 -1
  5. package/dist/index.umd.js +339 -155
  6. package/dist/index.umd.js.map +1 -1
  7. package/dist/types/collections.d.ts +4 -1
  8. package/dist/types/customization_controller.d.ts +8 -0
  9. package/dist/types/entity_actions.d.ts +45 -6
  10. package/dist/types/firecms.d.ts +8 -0
  11. package/dist/types/plugins.d.ts +8 -1
  12. package/dist/util/resolutions.d.ts +2 -1
  13. package/package.json +5 -5
  14. package/src/components/ConfirmationDialog.tsx +1 -0
  15. package/src/components/EntityCollectionTable/EntityCollectionRowActions.tsx +2 -0
  16. package/src/components/EntityCollectionTable/internal/CollectionTableToolbar.tsx +2 -2
  17. package/src/components/EntityCollectionView/EntityCollectionView.tsx +5 -2
  18. package/src/components/EntityCollectionView/EntityCollectionViewActions.tsx +3 -2
  19. package/src/components/HomePage/DefaultHomePage.tsx +48 -33
  20. package/src/components/HomePage/HomePageDnD.tsx +22 -36
  21. package/src/components/HomePage/RenameGroupDialog.tsx +6 -2
  22. package/src/components/UnsavedChangesDialog.tsx +6 -2
  23. package/src/components/common/default_entity_actions.tsx +18 -5
  24. package/src/components/common/useDataSourceTableController.tsx +1 -1
  25. package/src/core/EntityEditView.tsx +35 -12
  26. package/src/core/EntityEditViewFormActions.tsx +154 -29
  27. package/src/core/EntitySidePanel.tsx +1 -1
  28. package/src/core/FireCMS.tsx +2 -0
  29. package/src/form/EntityForm.tsx +31 -5
  30. package/src/form/EntityFormActions.tsx +37 -8
  31. package/src/form/field_bindings/MarkdownEditorFieldBinding.tsx +4 -2
  32. package/src/form/field_bindings/StorageUploadFieldBinding.tsx +1 -1
  33. package/src/types/collections.ts +4 -1
  34. package/src/types/customization_controller.tsx +9 -0
  35. package/src/types/entity_actions.tsx +56 -6
  36. package/src/types/firecms.tsx +9 -0
  37. package/src/types/plugins.tsx +9 -1
  38. package/src/util/join_collections.ts +3 -1
  39. package/src/util/resolutions.ts +13 -1
@@ -1,5 +1,12 @@
1
1
  import React from "react";
2
- import { Entity, EntityAction, FireCMSContext, ResolvedEntityCollection, SideEntityController } from "../types";
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} type="reset">
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"} color="primary" type="submit"
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-8text-text-secondary dark:text-text-secondary-dark ml-3.5"}/>}
117
+ className={"h-8 text-text-secondary dark:text-text-secondary-dark ml-3.5"}/>}
118
118
 
119
119
  <StorageUpload
120
120
  value={internalValue}
@@ -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: Entity<M>;
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
- onCollectionChange?: () => void;
108
+
63
109
  /**
64
- * If this actions is being called from a side dialog
110
+ * Optional function to navigate back (e.g. when deleting an entity or navigating from a form)
65
111
  */
66
- sideEntityController?: SideEntityController;
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
  };
@@ -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'
@@ -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({
@@ -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
- // .filter((e) => canEdit || !e.includeActions)
462
+ // .filter((e) => canEdit || !e.includeActions)
451
463
  : [];
452
464
 
453
465
  const selectedEntityView = resolvedEntityViews.find(e => e.key === selectedTab);