@firecms/core 3.0.0-rc.1 → 3.0.0-rc.3

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 (96) hide show
  1. package/dist/components/HomePage/HomePageDnD.d.ts +2 -1
  2. package/dist/components/PropertyCollectionView.d.ts +23 -0
  3. package/dist/components/UserDisplay.d.ts +7 -0
  4. package/dist/components/VirtualTable/fields/VirtualTableUserSelect.d.ts +12 -0
  5. package/dist/contexts/InternalUserManagementContext.d.ts +3 -0
  6. package/dist/core/EntityEditView.d.ts +10 -4
  7. package/dist/core/FireCMS.d.ts +0 -1
  8. package/dist/core/field_configs.d.ts +1 -1
  9. package/dist/form/EntityForm.d.ts +5 -2
  10. package/dist/form/components/LocalChangesMenu.d.ts +11 -0
  11. package/dist/form/field_bindings/UserSelectFieldBinding.d.ts +12 -0
  12. package/dist/form/index.d.ts +2 -1
  13. package/dist/hooks/index.d.ts +2 -0
  14. package/dist/hooks/useCollapsedGroups.d.ts +9 -0
  15. package/dist/hooks/useInternalUserManagementController.d.ts +12 -0
  16. package/dist/index.es.js +1983 -650
  17. package/dist/index.es.js.map +1 -1
  18. package/dist/index.umd.js +1981 -648
  19. package/dist/index.umd.js.map +1 -1
  20. package/dist/preview/components/UserPreview.d.ts +8 -0
  21. package/dist/preview/index.d.ts +1 -0
  22. package/dist/types/collections.d.ts +13 -0
  23. package/dist/types/entities.d.ts +5 -1
  24. package/dist/types/firecms.d.ts +15 -0
  25. package/dist/types/firecms_context.d.ts +16 -0
  26. package/dist/types/index.d.ts +1 -0
  27. package/dist/types/internal_user_management.d.ts +20 -0
  28. package/dist/types/plugins.d.ts +2 -0
  29. package/dist/types/properties.d.ts +41 -6
  30. package/dist/types/property_config.d.ts +1 -1
  31. package/dist/types/user.d.ts +1 -1
  32. package/dist/util/collections.d.ts +1 -0
  33. package/dist/util/entity_cache.d.ts +6 -1
  34. package/dist/util/make_properties_editable.d.ts +1 -2
  35. package/dist/util/objects.d.ts +1 -0
  36. package/dist/util/useStorageUploadController.d.ts +1 -0
  37. package/package.json +6 -6
  38. package/src/components/EntityCollectionTable/EntityCollectionRowActions.tsx +47 -47
  39. package/src/components/EntityCollectionTable/PropertyTableCell.tsx +12 -0
  40. package/src/components/EntityCollectionView/EntityCollectionView.tsx +6 -1
  41. package/src/components/EntityView.tsx +29 -40
  42. package/src/components/ErrorView.tsx +1 -1
  43. package/src/components/HomePage/DefaultHomePage.tsx +21 -34
  44. package/src/components/HomePage/HomePageDnD.tsx +143 -83
  45. package/src/components/HomePage/RenameGroupDialog.tsx +9 -3
  46. package/src/components/PropertyCollectionView.tsx +329 -0
  47. package/src/components/PropertyConfigBadge.tsx +2 -2
  48. package/src/components/SelectableTable/filters/DateTimeFilterField.tsx +2 -1
  49. package/src/components/SelectableTable/filters/StringNumberFilterField.tsx +1 -2
  50. package/src/components/UserDisplay.tsx +55 -0
  51. package/src/components/VirtualTable/fields/VirtualTableUserSelect.tsx +99 -0
  52. package/src/components/common/useColumnsIds.tsx +1 -8
  53. package/src/contexts/InternalUserManagementContext.tsx +4 -0
  54. package/src/core/EntityEditView.tsx +27 -14
  55. package/src/core/EntityEditViewFormActions.tsx +33 -18
  56. package/src/core/EntitySidePanel.tsx +9 -3
  57. package/src/core/FireCMS.tsx +22 -13
  58. package/src/core/field_configs.tsx +15 -1
  59. package/src/form/EntityForm.tsx +173 -42
  60. package/src/form/EntityFormActions.tsx +30 -15
  61. package/src/form/PropertyFieldBinding.tsx +4 -0
  62. package/src/form/components/ErrorFocus.tsx +22 -29
  63. package/src/form/components/LocalChangesMenu.tsx +144 -0
  64. package/src/form/field_bindings/UserSelectFieldBinding.tsx +94 -0
  65. package/src/form/index.tsx +5 -1
  66. package/src/hooks/index.tsx +3 -0
  67. package/src/hooks/useBrowserTitleAndIcon.tsx +1 -1
  68. package/src/hooks/useBuildNavigationController.tsx +104 -31
  69. package/src/hooks/useCollapsedGroups.ts +64 -0
  70. package/src/hooks/useFireCMSContext.tsx +6 -2
  71. package/src/hooks/useInternalUserManagementController.tsx +16 -0
  72. package/src/preview/PropertyPreview.tsx +8 -0
  73. package/src/preview/components/ReferencePreview.tsx +4 -2
  74. package/src/preview/components/UserPreview.tsx +27 -0
  75. package/src/preview/index.ts +1 -0
  76. package/src/preview/property_previews/ArrayPropertyPreview.tsx +1 -1
  77. package/src/preview/property_previews/MapPropertyPreview.tsx +2 -2
  78. package/src/preview/property_previews/NumberPropertyPreview.tsx +2 -2
  79. package/src/types/collections.ts +14 -0
  80. package/src/types/entities.ts +7 -1
  81. package/src/types/firecms.tsx +16 -0
  82. package/src/types/firecms_context.tsx +17 -0
  83. package/src/types/index.ts +1 -0
  84. package/src/types/internal_user_management.ts +24 -0
  85. package/src/types/plugins.tsx +3 -0
  86. package/src/types/properties.ts +45 -6
  87. package/src/types/property_config.tsx +1 -0
  88. package/src/types/user.ts +1 -1
  89. package/src/util/collections.ts +8 -0
  90. package/src/util/createFormexStub.tsx +4 -0
  91. package/src/util/entities.ts +1 -1
  92. package/src/util/entity_cache.ts +72 -53
  93. package/src/util/join_collections.ts +3 -3
  94. package/src/util/make_properties_editable.ts +0 -22
  95. package/src/util/objects.ts +40 -2
  96. package/src/util/useStorageUploadController.tsx +71 -34
@@ -0,0 +1,8 @@
1
+ import { PropertyPreviewProps } from "../PropertyPreviewProps";
2
+ /**
3
+ * Preview component for displaying user information.
4
+ * This is a simple wrapper around UserDisplay.
5
+ *
6
+ * @group Preview components
7
+ */
8
+ export declare function UserPreview({ value }: PropertyPreviewProps<string>): import("react/jsx-runtime").JSX.Element;
@@ -21,3 +21,4 @@ export * from "./components/EnumValuesChip";
21
21
  export * from "./components/EmptyValue";
22
22
  export * from "./components/ImagePreview";
23
23
  export * from "./components/ReferencePreview";
24
+ export * from "./components/UserPreview";
@@ -60,6 +60,8 @@ export interface EntityCollection<M extends Record<string, any> = any, USER exte
60
60
  * https://fonts.google.com/icons
61
61
  * e.g. 'account_tree' or 'person'.
62
62
  * Find all the icons in https://firecms.co/docs/icons
63
+ * You can also pass a React node if you want to render a custom icon.
64
+ * If not specified, a default icon will be used.
63
65
  */
64
66
  icon?: string | React.ReactNode;
65
67
  /**
@@ -305,6 +307,17 @@ export interface EntityCollection<M extends Record<string, any> = any, USER exte
305
307
  * This prop has no effect if the history plugin is not enabled
306
308
  */
307
309
  history?: boolean;
310
+ /**
311
+ * Should local changes be backed up in local storage, to prevent data loss on
312
+ * accidental navigations.
313
+ * - `manual_apply`: When the user navigates back to an entity with local changes,
314
+ * they will be prompted to restore the changes.
315
+ * - `auto_apply`: When the user navigates back to an entity with local changes,
316
+ * the changes will be automatically applied.
317
+ * - `false`: Local changes will not be backed up.
318
+ * Defaults to `manual_apply`.
319
+ */
320
+ localChangesBackup?: "manual_apply" | "auto_apply" | false;
308
321
  }
309
322
  /**
310
323
  * Parameter passed to the `Actions` prop in the collection configuration.
@@ -42,7 +42,11 @@ export declare class EntityReference {
42
42
  * to the root of the database).
43
43
  */
44
44
  readonly path: string;
45
- constructor(id: string, path: string);
45
+ /**
46
+ * Optional database ID where the entity is stored (if multiple databases are used)
47
+ */
48
+ readonly databaseId?: string;
49
+ constructor(id: string, path: string, databaseId?: string);
46
50
  get pathWithId(): string;
47
51
  isEntityReference(): boolean;
48
52
  }
@@ -13,6 +13,7 @@ import { UserConfigurationPersistence } from "./local_config_persistence";
13
13
  import { FireCMSPlugin } from "./plugins";
14
14
  import { CMSAnalyticsEvent } from "./analytics";
15
15
  import { EntityAction } from "./entity_actions";
16
+ import { InternalUserManagement } from "./internal_user_management";
16
17
  /**
17
18
  * Use this callback to build entity collections dynamically.
18
19
  * You can use the user to decide which collections to show.
@@ -123,6 +124,7 @@ export type FireCMSProps<USER extends User> = {
123
124
  * Use plugins to modify the behaviour of the CMS.
124
125
  * DEPRECATED: use the `plugins` prop in the `useBuildNavigationController` instead.
125
126
  * This prop will work as a fallback for the `plugins` prop in the `useBuildNavigationController`.
127
+ * @deprecated
126
128
  */
127
129
  plugins?: FireCMSPlugin<any, any, any>[];
128
130
  /**
@@ -134,6 +136,19 @@ export type FireCMSProps<USER extends User> = {
134
136
  * The function must return a URL that gets opened when the button is clicked
135
137
  */
136
138
  entityLinkBuilder?: EntityLinkBuilder;
139
+ /**
140
+ * You can use this props to provide your own user management implementation.
141
+ * Note that this will not affect the UI, but it will be used to show user information
142
+ * in various places of the CMS, for example, to show who created or modified an entity,
143
+ * or to assign ownership of an entity.
144
+ *
145
+ * You can also use this data to be retrieved in your custom properties,
146
+ * for example, to show a list of users in a dropdown.
147
+ *
148
+ * If you are using the FireCMS user management plugin, this
149
+ * prop will be implemented automatically.
150
+ */
151
+ userManagement?: InternalUserManagement;
137
152
  components?: {
138
153
  /**
139
154
  * Component to render when a reference is missing
@@ -10,6 +10,7 @@ import { SideDialogsController } from "./side_dialogs_controller";
10
10
  import { DialogsController } from "./dialogs_controller";
11
11
  import { CustomizationController } from "./customization_controller";
12
12
  import { AnalyticsController } from "./analytics_controller";
13
+ import { InternalUserManagement } from "./internal_user_management";
13
14
  /**
14
15
  * Context that includes the internal controllers and contexts used by the app.
15
16
  * Some controllers and context included in this context can be accessed
@@ -67,4 +68,19 @@ export type FireCMSContext<USER extends User = User, AuthControllerType extends
67
68
  * Callback to send analytics events
68
69
  */
69
70
  analyticsController?: AnalyticsController;
71
+ /**
72
+ * This section is used to manage users in the CMS.
73
+ * It is used to show user information in various places of the CMS,
74
+ * for example, to show who created or modified an entity,
75
+ * or to assign ownership of an entity.
76
+ *
77
+ * In the base CMS, this information is not used for access control.
78
+ * You can pass your own implementation of this section, to populate
79
+ * the dropdown of users when assigning ownership of an entity,
80
+ * or to show more information about the user.
81
+ *
82
+ * If you are using the FireCMS user management plugin, this
83
+ * section will be implemented automatically.
84
+ */
85
+ userManagement: InternalUserManagement<USER>;
70
86
  };
@@ -13,6 +13,7 @@ export * from "./fields";
13
13
  export * from "./property_config";
14
14
  export * from "./datasource";
15
15
  export * from "./entity_link_builder";
16
+ export * from "./internal_user_management";
16
17
  export * from "./side_entity_controller";
17
18
  export * from "./side_dialogs_controller";
18
19
  export * from "./firecms_context";
@@ -0,0 +1,20 @@
1
+ import { User } from "./user";
2
+ export type InternalUserManagement<USER extends User = User> = {
3
+ /**
4
+ * List of users to be managed by the CMS.
5
+ */
6
+ users: USER[];
7
+ /**
8
+ * Function to get a user by its uid. This is used to show
9
+ * user information when assigning ownership of an entity.
10
+ *
11
+ * You can pass your own implementation if you want to show
12
+ * more information about the user.
13
+ *
14
+ * If you are using the FireCMS user management plugin, this
15
+ * function will be implemented automatically.
16
+ *
17
+ * @param uid
18
+ */
19
+ getUser: (uid: string) => USER | null;
20
+ };
@@ -7,6 +7,7 @@ import { CMSType, Property } from "./properties";
7
7
  import { EntityStatus } from "./entities";
8
8
  import { ResolvedProperty } from "./resolved_entities";
9
9
  import { NavigationGroupMapping } from "./navigation";
10
+ import { InternalUserManagement } from "./internal_user_management";
10
11
  /**
11
12
  * Interface used to define plugins for FireCMS.
12
13
  * NOTE: This is a work in progress and the API is not stable yet.
@@ -37,6 +38,7 @@ export type FireCMSPlugin<PROPS = any, FORM_PROPS = any, EC extends EntityCollec
37
38
  }>>;
38
39
  props?: PROPS;
39
40
  };
41
+ userManagement?: InternalUserManagement;
40
42
  homePage?: {
41
43
  /**
42
44
  * Additional actions to be rendered in the home page, close to the search bar.
@@ -102,6 +102,10 @@ export interface BaseProperty<T extends CMSType, CustomProps = any> {
102
102
  /**
103
103
  * Should this property be editable. If set to true, the user will be able to modify the property and
104
104
  * save the new config. The saved config will then become the source of truth.
105
+ * Defaults to `true.
106
+ * This props is only useful when you are using the collection editor to modify collection
107
+ * configurations from the CMS itself. You can also use the `editable` prop in the
108
+ * `EntityCollection` interface to disable the edition of all properties in a collection.
105
109
  */
106
110
  editable?: boolean;
107
111
  /**
@@ -291,6 +295,15 @@ export interface StringProperty extends BaseProperty<string> {
291
295
  * indicate that this string refers to a path in your storage provider.
292
296
  */
293
297
  storage?: StorageConfig;
298
+ /**
299
+ * This property is used to indicate that the string is a user ID, and
300
+ * it will be rendered as a user picker.
301
+ * Note that the user ID needs to be the one used in your authentication
302
+ * provider, e.g. Firebase Auth.
303
+ * You can also use a property builder to specify the user path dynamically
304
+ * based on other values of the entity.
305
+ */
306
+ userSelect?: boolean;
294
307
  /**
295
308
  * If the value of this property is a URL, you can set this flag to true
296
309
  * to add a link, or one of the supported media types to render a preview
@@ -608,8 +621,15 @@ export type StorageConfig = {
608
621
  /**
609
622
  * Use client side image compression and resizing
610
623
  * Will only be applied to these MIME types: image/jpeg, image/png and image/webp
624
+ * @deprecated Use `imageResize` instead
611
625
  */
612
- imageCompression?: ImageCompression;
626
+ imageCompression?: ImageResize;
627
+ /**
628
+ * Advanced image resizing and cropping configuration.
629
+ * Applied before upload to optimize storage and bandwidth.
630
+ * Only applies to image MIME types: image/jpeg, image/png, image/webp
631
+ */
632
+ imageResize?: ImageResize;
613
633
  /**
614
634
  * Specific metadata set in your uploaded file.
615
635
  * For the default Firebase implementation, the values passed here are of type
@@ -721,17 +741,32 @@ export type PreviewType = "image" | "video" | "audio" | "file";
721
741
  * @group Entity properties
722
742
  */
723
743
  export type FileType = "image/*" | "video/*" | "audio/*" | "application/*" | "text/*" | "font/*" | string;
724
- export interface ImageCompression {
744
+ export interface ImageResize {
745
+ /**
746
+ * Maximum width in pixels. Image will be scaled down proportionally if wider.
747
+ */
748
+ maxWidth?: number;
725
749
  /**
726
- * New image max height (ratio is preserved)
750
+ * Maximum height in pixels. Image will be scaled down proportionally if taller.
727
751
  */
728
752
  maxHeight?: number;
729
753
  /**
730
- * New image max width (ratio is preserved)
754
+ * Resize mode determines how the image fits within maxWidth/maxHeight bounds.
755
+ * - `contain`: Scale down to fit within bounds, preserving aspect ratio (default)
756
+ * - `cover`: Scale to fill bounds, preserving aspect ratio (may crop)
731
757
  */
732
- maxWidth?: number;
758
+ mode?: 'contain' | 'cover';
759
+ /**
760
+ * Output format for the resized image.
761
+ * - `original`: Keep the original format (default)
762
+ * - `jpeg`: Convert to JPEG
763
+ * - `png`: Convert to PNG
764
+ * - `webp`: Convert to WebP
765
+ */
766
+ format?: 'original' | 'jpeg' | 'png' | 'webp';
733
767
  /**
734
- * A number between 0 and 100. Used for the JPEG compression.(if no compress is needed, just set it to 100)
768
+ * Quality for lossy formats (JPEG, WebP). Number between 0 and 100.
769
+ * Higher is better quality but larger file size. Defaults to 80.
735
770
  */
736
771
  quality?: number;
737
772
  }
@@ -37,4 +37,4 @@ export type PropertyConfig<T extends CMSType = any> = {
37
37
  */
38
38
  description?: string;
39
39
  };
40
- export type PropertyConfigId = "text_field" | "multiline" | "markdown" | "url" | "email" | "select" | "multi_select" | "number_input" | "number_select" | "multi_number_select" | "file_upload" | "multi_file_upload" | "group" | "key_value" | "reference" | "reference_as_string" | "multi_references" | "switch" | "date_time" | "repeat" | "custom_array" | "block";
40
+ export type PropertyConfigId = "text_field" | "multiline" | "markdown" | "url" | "email" | "user_select" | "select" | "multi_select" | "number_input" | "number_select" | "multi_number_select" | "file_upload" | "multi_file_upload" | "group" | "key_value" | "reference" | "reference_as_string" | "multi_references" | "switch" | "date_time" | "repeat" | "custom_array" | "block";
@@ -35,7 +35,7 @@ export type User = {
35
35
  */
36
36
  readonly isAnonymous: boolean;
37
37
  /**
38
- *
38
+ * Custom roles assigned to the user.
39
39
  */
40
40
  roles?: Role[];
41
41
  getIdToken?: (forceRefresh?: boolean) => Promise<string>;
@@ -9,3 +9,4 @@ export declare function resolveDefaultSelectedView(defaultSelectedView: string |
9
9
  * @param permissionsBuilder
10
10
  */
11
11
  export declare const applyPermissionsFunctionIfEmpty: (collections: EntityCollection[], permissionsBuilder?: PermissionsBuilder<any, any>) => EntityCollection[];
12
+ export declare function getLocalChangesBackup(collection: EntityCollection): "manual_apply" | "auto_apply";
@@ -4,14 +4,18 @@
4
4
  * @param data - The data to cache and persist.
5
5
  */
6
6
  export declare function saveEntityToCache(path: string, data: object): void;
7
+ export declare function removeEntityFromMemoryCache(path: string): void;
8
+ export declare function saveEntityToMemoryCache(path: string, data: object): void;
9
+ export declare function getEntityFromMemoryCache(path: string): object | undefined;
10
+ export declare function hasEntityInCache(path: string): boolean;
7
11
  /**
8
12
  * Retrieves an entity from the in-memory cache or `localStorage`.
9
13
  * If the entity is not in the cache but exists in `localStorage`, it loads it into the cache.
10
14
  * @param path - The unique path/key for the entity.
15
+ * @param useLocalStorage
11
16
  * @returns The cached entity or `undefined` if not found.
12
17
  */
13
18
  export declare function getEntityFromCache(path: string): object | undefined;
14
- export declare function hasEntityInCache(path: string): boolean;
15
19
  /**
16
20
  * Removes an entity from both the in-memory cache and `localStorage`.
17
21
  * @param path - The unique path/key for the entity to remove.
@@ -21,3 +25,4 @@ export declare function removeEntityFromCache(path: string): void;
21
25
  * Clears the entire in-memory cache and removes all related entities from `localStorage`.
22
26
  */
23
27
  export declare function clearEntityCache(): void;
28
+ export declare function flattenKeys(obj: any, prefix?: string, result?: string[]): string[];
@@ -1,3 +1,2 @@
1
- import { Properties, PropertiesOrBuilders } from "../types";
1
+ import { Properties } from "../types";
2
2
  export declare function makePropertiesEditable(properties: Properties): Properties<any>;
3
- export declare function makePropertiesNonEditable(properties: PropertiesOrBuilders): PropertiesOrBuilders;
@@ -1,5 +1,6 @@
1
1
  export declare const pick: <T>(obj: T, ...args: any[]) => T;
2
2
  export declare function isObject(item: any): any;
3
+ export declare function isPlainObject(obj: any): boolean;
3
4
  export declare function mergeDeep<T extends Record<any, any>, U extends Record<any, any>>(target: T, source: U, ignoreUndefined?: boolean): T & U;
4
5
  export declare function getValueInPath(o: object | undefined, path: string): any;
5
6
  export declare function removeInPath(o: object, path: string): object | undefined;
@@ -31,6 +31,7 @@ export declare function useStorageUploadController<M extends object>({ entityId,
31
31
  fileNameBuilder: (file: File) => Promise<string>;
32
32
  storagePathBuilder: (file: File) => string;
33
33
  onFileUploadComplete: (uploadedPath: string, entry: StorageFieldItem, metadata?: any) => Promise<void>;
34
+ onFileUploadError: (entry: StorageFieldItem) => void;
34
35
  onFilesAdded: (acceptedFiles: File[]) => Promise<void>;
35
36
  multipleFilesSupported: boolean;
36
37
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@firecms/core",
3
3
  "type": "module",
4
- "version": "3.0.0-rc.1",
4
+ "version": "3.0.0-rc.3",
5
5
  "description": "Awesome Firebase/Firestore-based headless open-source CMS",
6
6
  "funding": {
7
7
  "url": "https://github.com/sponsors/firecmsco"
@@ -53,11 +53,12 @@
53
53
  "@dnd-kit/core": "^6.3.1",
54
54
  "@dnd-kit/modifiers": "^9.0.0",
55
55
  "@dnd-kit/sortable": "^10.0.0",
56
- "@firecms/editor": "^3.0.0-rc.1",
57
- "@firecms/formex": "^3.0.0-rc.1",
58
- "@firecms/ui": "^3.0.0-rc.1",
56
+ "@firecms/editor": "^3.0.0-rc.3",
57
+ "@firecms/formex": "^3.0.0-rc.3",
58
+ "@firecms/ui": "^3.0.0-rc.3",
59
59
  "@radix-ui/react-portal": "^1.1.9",
60
60
  "clsx": "^2.1.1",
61
+ "compressorjs": "^1.2.1",
61
62
  "date-fns": "^3.6.0",
62
63
  "fuse.js": "^7.1.0",
63
64
  "history": "^5.3.0",
@@ -67,7 +68,6 @@
67
68
  "prism-react-renderer": "^2.4.1",
68
69
  "react-dropzone": "^14.3.8",
69
70
  "react-fast-compare": "^3.2.2",
70
- "react-image-file-resizer": "^0.4.8",
71
71
  "react-transition-group": "^4.4.5",
72
72
  "react-use-measure": "^2.1.7",
73
73
  "react-window": "^1.8.11",
@@ -108,7 +108,7 @@
108
108
  "dist",
109
109
  "src"
110
110
  ],
111
- "gitHead": "e91ae67d24262a9b944550abaae626f182688aa1",
111
+ "gitHead": "213f4c106eb3e6639eb1ad23c7d94282ef54d444",
112
112
  "publishConfig": {
113
113
  "access": "public"
114
114
  },
@@ -1,20 +1,10 @@
1
1
  import React, { MouseEvent, useCallback } from "react";
2
2
 
3
3
  import { CollectionSize, Entity, EntityAction, EntityCollection, SelectionController } from "../../types";
4
- import {
5
- Checkbox,
6
- Chip,
7
- cls,
8
- EditIcon,
9
- IconButton,
10
- Menu,
11
- MenuItem,
12
- MoreVertIcon,
13
- Skeleton,
14
- Tooltip
15
- } from "@firecms/ui";
4
+ import { Badge, Checkbox, cls, IconButton, Menu, MenuItem, MoreVertIcon, Skeleton, Tooltip } from "@firecms/ui";
16
5
  import { useFireCMSContext, useLargeLayout } from "../../hooks";
17
- import { hasEntityInCache } from "../../util/entity_cache";
6
+ import { getEntityFromCache } from "../../util/entity_cache";
7
+ import { getLocalChangesBackup } from "../../util";
18
8
 
19
9
  /**
20
10
  *
@@ -79,7 +69,9 @@ export const EntityCollectionRowActions = function EntityCollectionRowActions({
79
69
 
80
70
  const collapsedActions = actions.filter(a => a.collapsed || a.collapsed === undefined);
81
71
  const uncollapsedActions = actions.filter(a => a.collapsed === false);
82
- const hasDraft = hasEntityInCache(fullPath + "/" + entity.id);
72
+ const enableLocalChangesBackup = collection ? getLocalChangesBackup(collection) : false;
73
+ const hasDraft = enableLocalChangesBackup ? getEntityFromCache(fullPath + "/" + entity.id) : false;
74
+ const iconSize = largeLayout && (size === "m" || size === "l" || size == "xl") ? "medium" : "small";
83
75
  return (
84
76
  <div
85
77
  className={cls(
@@ -99,37 +91,50 @@ export const EntityCollectionRowActions = function EntityCollectionRowActions({
99
91
  {(hasActions || selectionEnabled) &&
100
92
  <div className="w-34 flex justify-center">
101
93
 
102
- {uncollapsedActions.map((action, index) => (
103
- <Tooltip key={index}
104
- title={action.name}
105
- asChild={true}>
106
- <IconButton
107
- onClick={(event: MouseEvent) => {
108
- event.stopPropagation();
109
- action.onClick({
110
- view: "collection",
111
- entity,
112
- fullPath,
113
- fullIdPath,
114
- collection,
115
- context,
116
- selectionController,
117
- highlightEntity,
118
- unhighlightEntity,
119
- onCollectionChange,
120
- openEntityMode: openEntityMode ?? collection?.openEntityMode
121
- });
122
- }}
123
- size={largeLayout ? "medium" : "small"}>
124
- {action.icon}
125
- </IconButton>
126
- </Tooltip>
127
- ))}
94
+ {uncollapsedActions.map((action, index) => {
95
+ const isEditAction = action.key === "edit";
96
+ const tooltip = isEditAction && hasDraft ? "Local unsaved changes" : action.name;
97
+
98
+ let iconButton = <IconButton
99
+ onClick={(event: MouseEvent) => {
100
+ event.stopPropagation();
101
+ action.onClick({
102
+ view: "collection",
103
+ entity,
104
+ fullPath,
105
+ fullIdPath,
106
+ collection,
107
+ context,
108
+ selectionController,
109
+ highlightEntity,
110
+ unhighlightEntity,
111
+ onCollectionChange,
112
+ openEntityMode: openEntityMode ?? collection?.openEntityMode
113
+ });
114
+ }}
115
+ size={iconSize}>
116
+ {action.icon}
117
+ </IconButton>;
118
+ if (isEditAction && hasDraft) {
119
+ iconButton = (
120
+ <Badge color={"warning"}>
121
+ {iconButton}
122
+ </Badge>
123
+ );
124
+ }
125
+ return (
126
+ <Tooltip key={index}
127
+ title={tooltip}
128
+ asChild={true}>
129
+ {iconButton}
130
+ </Tooltip>
131
+ );
132
+ })}
128
133
 
129
134
  {hasCollapsedActions &&
130
135
  <Menu
131
136
  trigger={<IconButton
132
- size={largeLayout ? "medium" : "small"}>
137
+ size={iconSize}>
133
138
  <MoreVertIcon/>
134
139
  </IconButton>}>
135
140
  {collapsedActions.map((action, index) => (
@@ -161,7 +166,7 @@ export const EntityCollectionRowActions = function EntityCollectionRowActions({
161
166
  {selectionEnabled &&
162
167
  <Tooltip title={`Select ${entity.id}`}>
163
168
  <Checkbox
164
- size={largeLayout ? "medium" : "small"}
169
+ size={iconSize}
165
170
  checked={Boolean(isSelected)}
166
171
  onCheckedChange={onCheckedChange}
167
172
  />
@@ -175,11 +180,6 @@ export const EntityCollectionRowActions = function EntityCollectionRowActions({
175
180
  onClick={(event) => {
176
181
  event.stopPropagation();
177
182
  }}>
178
- {hasDraft && <Tooltip title={"Local unsaved changes"} className={"inline"}>
179
- <Chip colorScheme={"orangeDarker"} className={"p-0.5"}>
180
- <EditIcon size={12}/>
181
- </Chip>
182
- </Tooltip>}
183
183
  <span className="min-w-0 truncate text-center">
184
184
  {entity
185
185
  ? entity.id
@@ -17,6 +17,7 @@ import { VirtualTableSelect } from "../VirtualTable/fields/VirtualTableSelect";
17
17
  import { VirtualTableNumberInput } from "../VirtualTable/fields/VirtualTableNumberInput";
18
18
  import { VirtualTableSwitch } from "../VirtualTable/fields/VirtualTableSwitch";
19
19
  import { VirtualTableDateField } from "../VirtualTable/fields/VirtualTableDateField";
20
+ import { VirtualTableUserSelect } from "../VirtualTable/fields/VirtualTableUserSelect";
20
21
 
21
22
  import { TableStorageUpload } from "./fields/TableStorageUpload";
22
23
  import { TableReferenceField } from "./fields/TableReferenceField";
@@ -332,6 +333,17 @@ export const PropertyTableCell = React.memo<PropertyTableCellProps<any>>(
332
333
  updateValue={updateValue}
333
334
  />;
334
335
  fullHeight = true;
336
+ } else if (stringProperty.userSelect) {
337
+ innerComponent = <VirtualTableUserSelect name={propertyKey as string}
338
+ multiple={false}
339
+ focused={selected}
340
+ disabled={disabled}
341
+ small={getPreviewSizeFrom(size) !== "medium"}
342
+ error={validationError ?? error}
343
+ internalValue={internalValue as string}
344
+ updateValue={updateValue}
345
+ />;
346
+ fullHeight = true;
335
347
  } else if (stringProperty.markdown || !stringProperty.storage || !stringProperty.reference) {
336
348
  const multiline = Boolean(stringProperty.multiline) || Boolean(stringProperty.markdown);
337
349
  innerComponent = <VirtualTableInput error={validationError ?? error}
@@ -376,6 +376,11 @@ export const EntityCollectionView = React.memo(
376
376
  console.error("Save failure");
377
377
  console.error(e);
378
378
  setError(e);
379
+ },
380
+ onPreSaveHookError: (e: Error) => {
381
+ console.error("Pre-save hook error");
382
+ console.error(e);
383
+ setError(e);
379
384
  }
380
385
  });
381
386
 
@@ -694,7 +699,7 @@ export const EntityCollectionView = React.memo(
694
699
  className="mt-4"
695
700
  >
696
701
  <AddIcon/>
697
- Create your first entity
702
+ Create your first entry
698
703
  </Button>
699
704
  </div>
700
705
  : <Typography variant={"label"}>No results with the applied filter/sort</Typography>
@@ -1,11 +1,11 @@
1
1
  import React, { useMemo } from "react";
2
- import { PropertyPreview } from "../preview";
3
2
  import { Entity, EntityCollection, ResolvedEntityCollection, ResolvedProperties } from "../types";
4
3
  import { resolveCollection } from "../util";
5
- import { cls, defaultBorderMixin, IconButton, OpenInNewIcon } from "@firecms/ui";
4
+ import { cls, defaultBorderMixin, IconButton, OpenInNewIcon, Typography } from "@firecms/ui";
6
5
  import { CustomizationController } from "../types/customization_controller";
7
6
  import { useCustomizationController } from "../hooks/useCustomizationController";
8
7
  import { useAuthController } from "../hooks";
8
+ import { PropertyCollectionView } from "./PropertyCollectionView";
9
9
 
10
10
  /**
11
11
  * @group Components
@@ -40,47 +40,36 @@ export function EntityView<M extends Record<string, any>>(
40
40
 
41
41
  return (
42
42
  <div className={"w-full " + className}>
43
- <div className={"w-full mb-4"}>
44
- <div className={cls(defaultBorderMixin, "flex justify-between py-2 border-b last:border-b-0")}>
45
- <div className="flex items-center w-1/4">
46
- <span className="pl-2 text-sm text-surface-600">Id</span>
43
+ <div className={"w-full mb-4 p-4"}>
44
+
45
+ <div className={`grid grid-cols-12 gap-x-4 py-4 items-start border-b ${defaultBorderMixin}`}>
46
+ <div className="col-span-4 pr-2">
47
+ <Typography variant="caption"
48
+ color={"secondary"}
49
+ component={"span"}
50
+ className="break-words">
51
+ Id
52
+ </Typography>
47
53
  </div>
48
- <div
49
- className="flex-grow p-2 ml-2 w-3/4 text-surface-900 dark:text-white min-h-[56px] flex items-center">
50
- <span className="flex-grow mr-2">{entity.id}</span>
51
- {customizationController?.entityLinkBuilder &&
52
- <a href={customizationController.entityLinkBuilder({ entity })}
53
- rel="noopener noreferrer"
54
- target="_blank">
55
- <IconButton>
56
- <OpenInNewIcon
57
- size={"small"}/>
58
- </IconButton>
59
- </a>}
54
+ <div className="col-span-8">
55
+ <div
56
+ className="flex-grow text-surface-900 dark:text-white flex items-center">
57
+ <span className="flex-grow mr-2">{entity.id}</span>
58
+ {customizationController?.entityLinkBuilder &&
59
+ <a href={customizationController.entityLinkBuilder({ entity })}
60
+ rel="noopener noreferrer"
61
+ target="_blank">
62
+ <IconButton>
63
+ <OpenInNewIcon
64
+ size={"small"}/>
65
+ </IconButton>
66
+ </a>}
67
+ </div>
60
68
  </div>
61
69
  </div>
62
- {Object.entries(properties)
63
- .map(([key, property]) => {
64
- const value = entity.values?.[key];
65
- return (
66
- <div
67
- key={`reference_previews_${key}`}
68
- className={cls(defaultBorderMixin, "flex justify-between py-2 border-b last:border-b-0")}>
69
- <div className="flex items-center w-1/4">
70
- <span className="pl-2 text-sm text-surface-600">{property.name}</span>
71
- </div>
72
- <div
73
- className="flex-grow p-2 ml-2 w-3/4 text-surface-900 dark:text-white min-h-[56px] flex items-center">
74
- <PropertyPreview
75
- propertyKey={key}
76
- value={value}
77
- // entity={entity}
78
- property={property}
79
- size={"medium"}/>
80
- </div>
81
- </div>
82
- )
83
- })}
70
+
71
+ <PropertyCollectionView data={entity.values} properties={properties} size={"medium"}/>
72
+
84
73
  </div>
85
74
  </div>
86
75
  );