@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.
@@ -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
  };
@@ -0,0 +1,2 @@
1
+ import { EntityAction } from "../types";
2
+ export declare function mergeEntityActions(currentActions: EntityAction[], newActions: EntityAction[]): EntityAction[];
@@ -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.165",
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.165",
54
- "@firecms/formex": "^3.0.0-canary.165",
55
- "@firecms/ui": "^3.0.0-canary.165",
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": "d6e4ccb9e524a6fac08b70f77eb9a188c894db24",
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.push(...customEntityActions);
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 className={cls("my-2 rounded-lg bg-surface-50 dark:bg-surface-950 text-surface-900 dark:text-white")}>
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(() => {
@@ -46,6 +46,7 @@ export const DialogsProvider: React.FC<PropsWithChildren<{}>> = ({ children }) =
46
46
  key={`dialog_${i}`}
47
47
  open={true}
48
48
  closeDialog={close}
49
+ {...entry.props}
49
50
  />)}
50
51
  </DialogsControllerContext.Provider>
51
52
  );
@@ -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.push(...customEntityActions);
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 (value === undefined || property === null) {
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;
@@ -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";