@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,27 @@
1
+ import React from "react";
2
+ import { PropertyPreviewProps } from "../PropertyPreviewProps";
3
+ import { useInternalUserManagementController } from "../../hooks";
4
+ import { UserDisplay } from "../../components/UserDisplay";
5
+ import { EmptyValue } from "./EmptyValue";
6
+ import { Typography } from "@firecms/ui";
7
+
8
+ /**
9
+ * Preview component for displaying user information.
10
+ * This is a simple wrapper around UserDisplay.
11
+ *
12
+ * @group Preview components
13
+ */
14
+ export function UserPreview({ value }: PropertyPreviewProps<string>) {
15
+ const { getUser } = useInternalUserManagementController();
16
+
17
+ if (!value) {
18
+ return <EmptyValue/>;
19
+ }
20
+
21
+ const user = getUser(value);
22
+ if (!user) {
23
+ return <Typography variant={"caption"} color={"secondary"}>User not found: {value}</Typography>;
24
+ }
25
+
26
+ return <UserDisplay user={user}/>;
27
+ }
@@ -25,3 +25,4 @@ export * from "./components/EnumValuesChip";
25
25
  export * from "./components/EmptyValue";
26
26
  export * from "./components/ImagePreview";
27
27
  export * from "./components/ReferencePreview";
28
+ export * from "./components/UserPreview";
@@ -42,7 +42,7 @@ export function ArrayPropertyPreview({
42
42
  const childSize: PreviewSize = size === "medium" ? "medium" : "small";
43
43
 
44
44
  return (
45
- <div className="flex flex-col gap-2">
45
+ <div className="w-full flex flex-col gap-2">
46
46
  {values &&
47
47
  values.map((value, index) => {
48
48
  const of: ResolvedProperty = property.resolvedProperties[index] ??
@@ -68,7 +68,7 @@ export function MapPropertyPreview<T extends Record<string, any> = Record<string
68
68
  <div
69
69
  className="min-w-[140px] w-[25%] py-1">
70
70
  <Typography variant={"caption"}
71
- className={"font-mono break-words"}
71
+ className={"break-words font-semibold"}
72
72
  color={"secondary"}>
73
73
  {childProperty.name}
74
74
  </Typography>
@@ -121,7 +121,7 @@ export function KeyValuePreview({ value }: { value: any }) {
121
121
  key={`table-cell-title-${key}-${key}`}
122
122
  className="min-w-[140px] w-[25%] py-1">
123
123
  <Typography variant={"caption"}
124
- className={"font-mono break-words"}
124
+ className={"font-semibold break-words"}
125
125
  color={"secondary"}>
126
126
  {key}
127
127
  </Typography>
@@ -16,12 +16,12 @@ export function NumberPropertyPreview({
16
16
  const enumKey = value;
17
17
  const enumValues = enumToObjectEntries(property.enumValues);
18
18
  if (!enumValues)
19
- return <>{value}</>;
19
+ return <span className={size === "small" ? "text-sm" : ""}>{value}</span>;
20
20
  return <EnumValuesChip
21
21
  enumKey={enumKey}
22
22
  enumValues={enumValues}
23
23
  size={size !== "medium" ? "small" : "medium"}/>;
24
24
  } else {
25
- return <>{value}</>;
25
+ return <span className={size === "small" ? "text-sm" : ""}>{value}</span>;
26
26
  }
27
27
  }
@@ -69,6 +69,8 @@ export interface EntityCollection<M extends Record<string, any> = any, USER exte
69
69
  * https://fonts.google.com/icons
70
70
  * e.g. 'account_tree' or 'person'.
71
71
  * Find all the icons in https://firecms.co/docs/icons
72
+ * You can also pass a React node if you want to render a custom icon.
73
+ * If not specified, a default icon will be used.
72
74
  */
73
75
  icon?: string | React.ReactNode;
74
76
 
@@ -350,6 +352,18 @@ export interface EntityCollection<M extends Record<string, any> = any, USER exte
350
352
  * This prop has no effect if the history plugin is not enabled
351
353
  */
352
354
  history?: boolean;
355
+
356
+ /**
357
+ * Should local changes be backed up in local storage, to prevent data loss on
358
+ * accidental navigations.
359
+ * - `manual_apply`: When the user navigates back to an entity with local changes,
360
+ * they will be prompted to restore the changes.
361
+ * - `auto_apply`: When the user navigates back to an entity with local changes,
362
+ * the changes will be automatically applied.
363
+ * - `false`: Local changes will not be backed up.
364
+ * Defaults to `manual_apply`.
365
+ */
366
+ localChangesBackup?: "manual_apply" | "auto_apply" | false;
353
367
  }
354
368
 
355
369
  /**
@@ -50,9 +50,15 @@ export class EntityReference {
50
50
  */
51
51
  readonly path: string;
52
52
 
53
- constructor(id: string, path: string) {
53
+ /**
54
+ * Optional database ID where the entity is stored (if multiple databases are used)
55
+ */
56
+ readonly databaseId?: string;
57
+
58
+ constructor(id: string, path: string, databaseId?: string) {
54
59
  this.id = id;
55
60
  this.path = path;
61
+ this.databaseId = databaseId;
56
62
  }
57
63
 
58
64
  get pathWithId() {
@@ -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
  /**
18
19
  * Use this callback to build entity collections dynamically.
@@ -139,6 +140,7 @@ export type FireCMSProps<USER extends User> = {
139
140
  * Use plugins to modify the behaviour of the CMS.
140
141
  * DEPRECATED: use the `plugins` prop in the `useBuildNavigationController` instead.
141
142
  * This prop will work as a fallback for the `plugins` prop in the `useBuildNavigationController`.
143
+ * @deprecated
142
144
  */
143
145
  plugins?: FireCMSPlugin<any, any, any>[];
144
146
 
@@ -153,6 +155,20 @@ export type FireCMSProps<USER extends User> = {
153
155
  */
154
156
  entityLinkBuilder?: EntityLinkBuilder;
155
157
 
158
+ /**
159
+ * You can use this props to provide your own user management implementation.
160
+ * Note that this will not affect the UI, but it will be used to show user information
161
+ * in various places of the CMS, for example, to show who created or modified an entity,
162
+ * or to assign ownership of an entity.
163
+ *
164
+ * You can also use this data to be retrieved in your custom properties,
165
+ * for example, to show a list of users in a dropdown.
166
+ *
167
+ * If you are using the FireCMS user management plugin, this
168
+ * prop will be implemented automatically.
169
+ */
170
+ userManagement?: InternalUserManagement
171
+
156
172
  components?: {
157
173
 
158
174
  /**
@@ -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
  /**
15
16
  * Context that includes the internal controllers and contexts used by the app.
@@ -80,4 +81,20 @@ export type FireCMSContext<USER extends User = User, AuthControllerType extends
80
81
  */
81
82
  analyticsController?: AnalyticsController;
82
83
 
84
+ /**
85
+ * This section is used to manage users in the CMS.
86
+ * It is used to show user information in various places of the CMS,
87
+ * for example, to show who created or modified an entity,
88
+ * or to assign ownership of an entity.
89
+ *
90
+ * In the base CMS, this information is not used for access control.
91
+ * You can pass your own implementation of this section, to populate
92
+ * the dropdown of users when assigning ownership of an entity,
93
+ * or to show more information about the user.
94
+ *
95
+ * If you are using the FireCMS user management plugin, this
96
+ * section will be implemented automatically.
97
+ */
98
+ userManagement: InternalUserManagement<USER>
99
+
83
100
  };
@@ -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,24 @@
1
+ import { User } from "./user";
2
+
3
+ export type InternalUserManagement<USER extends User = User> = {
4
+
5
+ /**
6
+ * List of users to be managed by the CMS.
7
+ */
8
+ users: USER[];
9
+
10
+ /**
11
+ * Function to get a user by its uid. This is used to show
12
+ * user information when assigning ownership of an entity.
13
+ *
14
+ * You can pass your own implementation if you want to show
15
+ * more information about the user.
16
+ *
17
+ * If you are using the FireCMS user management plugin, this
18
+ * function will be implemented automatically.
19
+ *
20
+ * @param uid
21
+ */
22
+ getUser: (uid: string) => USER | null;
23
+
24
+ }
@@ -8,6 +8,7 @@ import { CMSType, Property } from "./properties";
8
8
  import { EntityStatus } from "./entities";
9
9
  import { ResolvedProperty } from "./resolved_entities";
10
10
  import { NavigationGroupMapping } from "./navigation";
11
+ import { InternalUserManagement } from "./internal_user_management";
11
12
 
12
13
  /**
13
14
  * Interface used to define plugins for FireCMS.
@@ -43,6 +44,8 @@ export type FireCMSPlugin<PROPS = any, FORM_PROPS = any, EC extends EntityCollec
43
44
  props?: PROPS;
44
45
  };
45
46
 
47
+ userManagement?: InternalUserManagement
48
+
46
49
  homePage?: {
47
50
 
48
51
  /**
@@ -155,6 +155,10 @@ export interface BaseProperty<T extends CMSType, CustomProps = any> {
155
155
  /**
156
156
  * Should this property be editable. If set to true, the user will be able to modify the property and
157
157
  * save the new config. The saved config will then become the source of truth.
158
+ * Defaults to `true.
159
+ * This props is only useful when you are using the collection editor to modify collection
160
+ * configurations from the CMS itself. You can also use the `editable` prop in the
161
+ * `EntityCollection` interface to disable the edition of all properties in a collection.
158
162
  */
159
163
  editable?: boolean;
160
164
 
@@ -388,6 +392,16 @@ export interface StringProperty extends BaseProperty<string> {
388
392
  */
389
393
  storage?: StorageConfig;
390
394
 
395
+ /**
396
+ * This property is used to indicate that the string is a user ID, and
397
+ * it will be rendered as a user picker.
398
+ * Note that the user ID needs to be the one used in your authentication
399
+ * provider, e.g. Firebase Auth.
400
+ * You can also use a property builder to specify the user path dynamically
401
+ * based on other values of the entity.
402
+ */
403
+ userSelect?: boolean;
404
+
391
405
  /**
392
406
  * If the value of this property is a URL, you can set this flag to true
393
407
  * to add a link, or one of the supported media types to render a preview
@@ -765,8 +779,16 @@ export type StorageConfig = {
765
779
  /**
766
780
  * Use client side image compression and resizing
767
781
  * Will only be applied to these MIME types: image/jpeg, image/png and image/webp
782
+ * @deprecated Use `imageResize` instead
768
783
  */
769
- imageCompression?: ImageCompression;
784
+ imageCompression?: ImageResize;
785
+
786
+ /**
787
+ * Advanced image resizing and cropping configuration.
788
+ * Applied before upload to optimize storage and bandwidth.
789
+ * Only applies to image MIME types: image/jpeg, image/png, image/webp
790
+ */
791
+ imageResize?: ImageResize;
770
792
 
771
793
  /**
772
794
  * Specific metadata set in your uploaded file.
@@ -903,19 +925,36 @@ export type FileType =
903
925
  | "font/*"
904
926
  | string;
905
927
 
906
- export interface ImageCompression {
928
+ export interface ImageResize {
929
+ /**
930
+ * Maximum width in pixels. Image will be scaled down proportionally if wider.
931
+ */
932
+ maxWidth?: number;
933
+
907
934
  /**
908
- * New image max height (ratio is preserved)
935
+ * Maximum height in pixels. Image will be scaled down proportionally if taller.
909
936
  */
910
937
  maxHeight?: number;
911
938
 
912
939
  /**
913
- * New image max width (ratio is preserved)
940
+ * Resize mode determines how the image fits within maxWidth/maxHeight bounds.
941
+ * - `contain`: Scale down to fit within bounds, preserving aspect ratio (default)
942
+ * - `cover`: Scale to fill bounds, preserving aspect ratio (may crop)
914
943
  */
915
- maxWidth?: number;
944
+ mode?: 'contain' | 'cover';
945
+
946
+ /**
947
+ * Output format for the resized image.
948
+ * - `original`: Keep the original format (default)
949
+ * - `jpeg`: Convert to JPEG
950
+ * - `png`: Convert to PNG
951
+ * - `webp`: Convert to WebP
952
+ */
953
+ format?: 'original' | 'jpeg' | 'png' | 'webp';
916
954
 
917
955
  /**
918
- * A number between 0 and 100. Used for the JPEG compression.(if no compress is needed, just set it to 100)
956
+ * Quality for lossy formats (JPEG, WebP). Number between 0 and 100.
957
+ * Higher is better quality but larger file size. Defaults to 80.
919
958
  */
920
959
  quality?: number;
921
960
  }
@@ -52,6 +52,7 @@ export type PropertyConfigId =
52
52
  "markdown" |
53
53
  "url" |
54
54
  "email" |
55
+ "user_select" |
55
56
  "select" |
56
57
  "multi_select" |
57
58
  "number_input" |
package/src/types/user.ts CHANGED
@@ -37,7 +37,7 @@ export type User = {
37
37
  readonly isAnonymous: boolean;
38
38
 
39
39
  /**
40
- *
40
+ * Custom roles assigned to the user.
41
41
  */
42
42
  roles?: Role[];
43
43
 
@@ -70,3 +70,11 @@ export const applyPermissionsFunctionIfEmpty = (collections: EntityCollection[],
70
70
  });
71
71
  });
72
72
  }
73
+
74
+ export function getLocalChangesBackup(collection: EntityCollection) {
75
+ if (!collection.localChangesBackup) {
76
+ return "manual_apply";
77
+ }
78
+
79
+ return collection.localChangesBackup;
80
+ }
@@ -4,6 +4,7 @@ export function createFormexStub<T extends object>(values: T): FormexController<
4
4
  const errorMessage = "You are in a read-only context. You cannot modify the formex controller.";
5
5
 
6
6
  return {
7
+ debugId: "",
7
8
  values,
8
9
  initialValues: values,
9
10
  touched: {} as Record<string, boolean>,
@@ -19,6 +20,9 @@ export function createFormexStub<T extends object>(values: T): FormexController<
19
20
  setValues: () => {
20
21
  throw new Error(errorMessage);
21
22
  },
23
+ setTouched(touched: Record<string, boolean>): void {
24
+ throw new Error(errorMessage);
25
+ },
22
26
  setFieldValue: () => {
23
27
  throw new Error(errorMessage);
24
28
  },
@@ -142,7 +142,7 @@ export function sanitizeData<M extends Record<string, any>>
142
142
  }
143
143
 
144
144
  export function getReferenceFrom<M extends Record<string, any>>(entity: Entity<M>): EntityReference {
145
- return new EntityReference(entity.id, entity.path);
145
+ return new EntityReference(entity.id, entity.path, entity.databaseId);
146
146
  }
147
147
 
148
148
  export function traverseValuesProperties<M extends Record<string, any>>(
@@ -1,4 +1,5 @@
1
1
  import { EntityReference, GeoPoint, Vector } from "../types";
2
+ import { isObject, isPlainObject } from "./objects";
2
3
 
3
4
  // Define a unique prefix for entity keys in localStorage to avoid key collisions
4
5
  const LOCAL_STORAGE_PREFIX = "entity_cache::";
@@ -18,25 +19,40 @@ function customReplacer(key: string): any {
18
19
  // Handle Date objects
19
20
  // @ts-ignore
20
21
  if (value instanceof Date) {
21
- return { __type: "Date", value: value.toISOString() };
22
+ return {
23
+ __type: "Date",
24
+ value: value.toISOString()
25
+ };
22
26
  }
23
27
 
24
28
  // Handle EntityReference
25
29
  // @ts-ignore
26
30
  if (value instanceof EntityReference) {
27
- return { __type: "EntityReference", id: value.id, path: value.path };
31
+ return {
32
+ __type: "EntityReference",
33
+ id: value.id,
34
+ path: value.path,
35
+ databaseId: value.databaseId
36
+ };
28
37
  }
29
38
 
30
39
  // Handle GeoPoint
31
40
  // @ts-ignore
32
41
  if (value instanceof GeoPoint) {
33
- return { __type: "GeoPoint", latitude: value.latitude, longitude: value.longitude };
42
+ return {
43
+ __type: "GeoPoint",
44
+ latitude: value.latitude,
45
+ longitude: value.longitude
46
+ };
34
47
  }
35
48
 
36
49
  // Handle Vector
37
50
  // @ts-ignore
38
51
  if (value instanceof Vector) {
39
- return { __type: "Vector", value: value.value };
52
+ return {
53
+ __type: "Vector",
54
+ value: value.value
55
+ };
40
56
  }
41
57
 
42
58
  return value;
@@ -49,7 +65,7 @@ function customReviver(key: string, value: any): any {
49
65
  case "Date":
50
66
  return new Date(value.value);
51
67
  case "EntityReference":
52
- return new EntityReference(value.id, value.path);
68
+ return new EntityReference(value.id, value.path, value.databaseId);
53
69
  case "GeoPoint":
54
70
  return new GeoPoint(value.latitude, value.longitude);
55
71
  case "Vector":
@@ -61,47 +77,21 @@ function customReviver(key: string, value: any): any {
61
77
  return value;
62
78
  }
63
79
 
64
- // Initialize the in-memory cache by loading entities from `localStorage`
65
- if (isLocalStorageAvailable) {
66
- try {
67
- // Iterate over all keys in localStorage to find those with the specified prefix
68
- for (let i = 0; i < localStorage.length; i++) {
69
- const fullKey = localStorage.key(i);
70
- if (fullKey && fullKey.startsWith(LOCAL_STORAGE_PREFIX)) {
71
- const path = fullKey.substring(LOCAL_STORAGE_PREFIX.length);
72
- const entityString = localStorage.getItem(fullKey);
73
- if (entityString) {
74
- try {
75
- const entity: object = JSON.parse(entityString, customReviver);
76
- entityCache.set(path, entity);
77
- } catch (parseError) {
78
- console.error(
79
- `Failed to parse entity for path "${path}" from localStorage:`,
80
- parseError
81
- );
82
- }
83
- }
84
- }
85
- }
86
- } catch (error) {
87
- console.error("Error accessing localStorage during initialization:", error);
88
- }
89
- }
90
-
91
80
  /**
92
81
  * Saves data to the in-memory cache and persists it individually in `localStorage`.
93
82
  * @param path - The unique path/key for the data.
94
83
  * @param data - The data to cache and persist.
95
84
  */
96
85
  export function saveEntityToCache(path: string, data: object): void {
97
- // Update the in-memory cache
98
- entityCache.set(path, data);
99
-
100
86
  // Persist the data individually in localStorage
101
87
  if (isLocalStorageAvailable) {
102
88
  try {
103
89
  const key = LOCAL_STORAGE_PREFIX + path;
104
90
  const entityString = JSON.stringify(data, customReplacer);
91
+ console.log("Saving entity to localStorage:", {
92
+ key,
93
+ entityString
94
+ });
105
95
  localStorage.setItem(key, entityString);
106
96
  } catch (error) {
107
97
  console.error(
@@ -112,19 +102,31 @@ export function saveEntityToCache(path: string, data: object): void {
112
102
  }
113
103
  }
114
104
 
105
+ export function removeEntityFromMemoryCache(path: string): void {
106
+ entityCache.delete(path);
107
+ }
108
+
109
+ export function saveEntityToMemoryCache(path: string, data: object): void {
110
+ entityCache.set(path, data);
111
+ }
112
+
113
+ export function getEntityFromMemoryCache(path: string): object | undefined {
114
+ return entityCache.get(path);
115
+ }
116
+
117
+ export function hasEntityInCache(path: string): boolean {
118
+ return entityCache.has(path);
119
+ }
120
+
115
121
  /**
116
122
  * Retrieves an entity from the in-memory cache or `localStorage`.
117
123
  * If the entity is not in the cache but exists in `localStorage`, it loads it into the cache.
118
124
  * @param path - The unique path/key for the entity.
125
+ * @param useLocalStorage
119
126
  * @returns The cached entity or `undefined` if not found.
120
127
  */
121
128
  export function getEntityFromCache(path: string): object | undefined {
122
129
 
123
- // Attempt to retrieve the entity from the in-memory cache
124
- if (entityCache.has(path)) {
125
- return entityCache.get(path);
126
- }
127
-
128
130
  // If not in the cache, attempt to load it from localStorage
129
131
  if (isLocalStorageAvailable) {
130
132
  try {
@@ -132,7 +134,10 @@ export function getEntityFromCache(path: string): object | undefined {
132
134
  const entityString = localStorage.getItem(key);
133
135
  if (entityString) {
134
136
  const entity: object = JSON.parse(entityString, customReviver);
135
- entityCache.set(path, entity); // Update the cache
137
+ console.log("Loaded entity from localStorage:", {
138
+ key,
139
+ entity
140
+ });
136
141
  return entity;
137
142
  }
138
143
  } catch (error) {
@@ -147,23 +152,11 @@ export function getEntityFromCache(path: string): object | undefined {
147
152
  return undefined;
148
153
  }
149
154
 
150
- export function hasEntityInCache(path: string): boolean {
151
- return entityCache.has(path);
152
- }
153
-
154
155
  /**
155
156
  * Removes an entity from both the in-memory cache and `localStorage`.
156
157
  * @param path - The unique path/key for the entity to remove.
157
158
  */
158
159
  export function removeEntityFromCache(path: string): void {
159
-
160
-
161
- console.debug("Removing entity from cache", path);
162
-
163
- // Remove from the in-memory cache
164
- entityCache.delete(path);
165
-
166
- // Remove from localStorage
167
160
  if (isLocalStorageAvailable) {
168
161
  try {
169
162
  const key = LOCAL_STORAGE_PREFIX + path;
@@ -202,3 +195,29 @@ export function clearEntityCache(): void {
202
195
  }
203
196
  }
204
197
  }
198
+
199
+ export function flattenKeys(obj: any, prefix = "", result: string[] = []): string[] {
200
+
201
+ if (isObject(obj) || Array.isArray(obj)) {
202
+ const plainObject = isPlainObject(obj);
203
+ if (!plainObject && prefix) {
204
+ result.push(prefix);
205
+ } else {
206
+ for (const key in obj) {
207
+ if (Object.prototype.hasOwnProperty.call(obj, key)) {
208
+ const newKey = prefix
209
+ ? Array.isArray(obj)
210
+ ? `${prefix}[${key}]`
211
+ : `${prefix}.${key}`
212
+ : key;
213
+ if (isObject(obj[key]) || Array.isArray(obj[key])) {
214
+ flattenKeys(obj[key], newKey, result);
215
+ } else {
216
+ result.push(newKey);
217
+ }
218
+ }
219
+ }
220
+ }
221
+ }
222
+ return result;
223
+ }
@@ -78,7 +78,6 @@ export function mergeCollection(target: EntityCollection,
78
78
  modifyCollection?: (props: ModifyCollectionProps) => EntityCollection | void
79
79
  ): EntityCollection {
80
80
 
81
-
82
81
  const subcollectionsMerged = joinCollectionLists(
83
82
  target?.subcollections ?? [],
84
83
  source?.subcollections ?? [],
@@ -132,8 +131,9 @@ function mergePropertyOrBuilder(target: PropertyOrBuilder, source: PropertyOrBui
132
131
  return target;
133
132
  } else {
134
133
  const mergedProperty = mergeDeep(target, source);
135
- const targetEditable = Boolean(target.editable);
136
- const sourceEditable = Boolean(source.editable);
134
+ const targetEditable = target.editable === undefined ? true : Boolean(target.editable);
135
+ const sourceEditable = source.editable === undefined ? true : Boolean(source.editable);
136
+
137
137
  if (source.dataType === "map" && source.properties) {
138
138
  const targetProperties = ("properties" in target ? target.properties : {}) as PropertiesOrBuilders;
139
139
  const sourceProperties = ("properties" in source ? source.properties : {}) as PropertiesOrBuilders;
@@ -13,25 +13,3 @@ export function makePropertiesEditable(properties: Properties) {
13
13
  });
14
14
  return properties;
15
15
  }
16
-
17
- export function makePropertiesNonEditable(properties: PropertiesOrBuilders): PropertiesOrBuilders {
18
- return Object.entries(properties).reduce((acc, [key, property]) => {
19
- if (!isPropertyBuilder(property) && property.dataType === "map" && property.properties) {
20
- const updated = {
21
- ...property,
22
- properties: makePropertiesNonEditable(property.properties as PropertiesOrBuilders)
23
- };
24
- acc[key] = updated;
25
- }
26
- if (isPropertyBuilder(property)) {
27
- acc[key] = property;
28
- } else {
29
- acc[key] = {
30
- ...property,
31
- editable: false
32
- };
33
- }
34
- return acc;
35
- }, {} as PropertiesOrBuilders);
36
-
37
- }