@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.
- package/dist/components/HomePage/HomePageDnD.d.ts +2 -1
- package/dist/components/PropertyCollectionView.d.ts +23 -0
- package/dist/components/UserDisplay.d.ts +7 -0
- package/dist/components/VirtualTable/fields/VirtualTableUserSelect.d.ts +12 -0
- package/dist/contexts/InternalUserManagementContext.d.ts +3 -0
- package/dist/core/EntityEditView.d.ts +10 -4
- package/dist/core/FireCMS.d.ts +0 -1
- package/dist/core/field_configs.d.ts +1 -1
- package/dist/form/EntityForm.d.ts +5 -2
- package/dist/form/components/LocalChangesMenu.d.ts +11 -0
- package/dist/form/field_bindings/UserSelectFieldBinding.d.ts +12 -0
- package/dist/form/index.d.ts +2 -1
- package/dist/hooks/index.d.ts +2 -0
- package/dist/hooks/useCollapsedGroups.d.ts +9 -0
- package/dist/hooks/useInternalUserManagementController.d.ts +12 -0
- package/dist/index.es.js +1983 -650
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +1981 -648
- package/dist/index.umd.js.map +1 -1
- package/dist/preview/components/UserPreview.d.ts +8 -0
- package/dist/preview/index.d.ts +1 -0
- package/dist/types/collections.d.ts +13 -0
- package/dist/types/entities.d.ts +5 -1
- package/dist/types/firecms.d.ts +15 -0
- package/dist/types/firecms_context.d.ts +16 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/internal_user_management.d.ts +20 -0
- package/dist/types/plugins.d.ts +2 -0
- package/dist/types/properties.d.ts +41 -6
- package/dist/types/property_config.d.ts +1 -1
- package/dist/types/user.d.ts +1 -1
- package/dist/util/collections.d.ts +1 -0
- package/dist/util/entity_cache.d.ts +6 -1
- package/dist/util/make_properties_editable.d.ts +1 -2
- package/dist/util/objects.d.ts +1 -0
- package/dist/util/useStorageUploadController.d.ts +1 -0
- package/package.json +6 -6
- package/src/components/EntityCollectionTable/EntityCollectionRowActions.tsx +47 -47
- package/src/components/EntityCollectionTable/PropertyTableCell.tsx +12 -0
- package/src/components/EntityCollectionView/EntityCollectionView.tsx +6 -1
- package/src/components/EntityView.tsx +29 -40
- package/src/components/ErrorView.tsx +1 -1
- package/src/components/HomePage/DefaultHomePage.tsx +21 -34
- package/src/components/HomePage/HomePageDnD.tsx +143 -83
- package/src/components/HomePage/RenameGroupDialog.tsx +9 -3
- package/src/components/PropertyCollectionView.tsx +329 -0
- package/src/components/PropertyConfigBadge.tsx +2 -2
- package/src/components/SelectableTable/filters/DateTimeFilterField.tsx +2 -1
- package/src/components/SelectableTable/filters/StringNumberFilterField.tsx +1 -2
- package/src/components/UserDisplay.tsx +55 -0
- package/src/components/VirtualTable/fields/VirtualTableUserSelect.tsx +99 -0
- package/src/components/common/useColumnsIds.tsx +1 -8
- package/src/contexts/InternalUserManagementContext.tsx +4 -0
- package/src/core/EntityEditView.tsx +27 -14
- package/src/core/EntityEditViewFormActions.tsx +33 -18
- package/src/core/EntitySidePanel.tsx +9 -3
- package/src/core/FireCMS.tsx +22 -13
- package/src/core/field_configs.tsx +15 -1
- package/src/form/EntityForm.tsx +173 -42
- package/src/form/EntityFormActions.tsx +30 -15
- package/src/form/PropertyFieldBinding.tsx +4 -0
- package/src/form/components/ErrorFocus.tsx +22 -29
- package/src/form/components/LocalChangesMenu.tsx +144 -0
- package/src/form/field_bindings/UserSelectFieldBinding.tsx +94 -0
- package/src/form/index.tsx +5 -1
- package/src/hooks/index.tsx +3 -0
- package/src/hooks/useBrowserTitleAndIcon.tsx +1 -1
- package/src/hooks/useBuildNavigationController.tsx +104 -31
- package/src/hooks/useCollapsedGroups.ts +64 -0
- package/src/hooks/useFireCMSContext.tsx +6 -2
- package/src/hooks/useInternalUserManagementController.tsx +16 -0
- package/src/preview/PropertyPreview.tsx +8 -0
- package/src/preview/components/ReferencePreview.tsx +4 -2
- package/src/preview/components/UserPreview.tsx +27 -0
- package/src/preview/index.ts +1 -0
- package/src/preview/property_previews/ArrayPropertyPreview.tsx +1 -1
- package/src/preview/property_previews/MapPropertyPreview.tsx +2 -2
- package/src/preview/property_previews/NumberPropertyPreview.tsx +2 -2
- package/src/types/collections.ts +14 -0
- package/src/types/entities.ts +7 -1
- package/src/types/firecms.tsx +16 -0
- package/src/types/firecms_context.tsx +17 -0
- package/src/types/index.ts +1 -0
- package/src/types/internal_user_management.ts +24 -0
- package/src/types/plugins.tsx +3 -0
- package/src/types/properties.ts +45 -6
- package/src/types/property_config.tsx +1 -0
- package/src/types/user.ts +1 -1
- package/src/util/collections.ts +8 -0
- package/src/util/createFormexStub.tsx +4 -0
- package/src/util/entities.ts +1 -1
- package/src/util/entity_cache.ts +72 -53
- package/src/util/join_collections.ts +3 -3
- package/src/util/make_properties_editable.ts +0 -22
- package/src/util/objects.ts +40 -2
- 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
|
+
}
|
package/src/preview/index.ts
CHANGED
|
@@ -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={"
|
|
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-
|
|
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
|
|
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
|
|
25
|
+
return <span className={size === "small" ? "text-sm" : ""}>{value}</span>;
|
|
26
26
|
}
|
|
27
27
|
}
|
package/src/types/collections.ts
CHANGED
|
@@ -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
|
/**
|
package/src/types/entities.ts
CHANGED
|
@@ -50,9 +50,15 @@ export class EntityReference {
|
|
|
50
50
|
*/
|
|
51
51
|
readonly path: string;
|
|
52
52
|
|
|
53
|
-
|
|
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() {
|
package/src/types/firecms.tsx
CHANGED
|
@@ -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
|
};
|
package/src/types/index.ts
CHANGED
|
@@ -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
|
+
}
|
package/src/types/plugins.tsx
CHANGED
|
@@ -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
|
/**
|
package/src/types/properties.ts
CHANGED
|
@@ -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?:
|
|
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
|
|
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
|
-
*
|
|
935
|
+
* Maximum height in pixels. Image will be scaled down proportionally if taller.
|
|
909
936
|
*/
|
|
910
937
|
maxHeight?: number;
|
|
911
938
|
|
|
912
939
|
/**
|
|
913
|
-
*
|
|
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
|
-
|
|
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
|
-
*
|
|
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
|
}
|
package/src/types/user.ts
CHANGED
package/src/util/collections.ts
CHANGED
|
@@ -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
|
},
|
package/src/util/entities.ts
CHANGED
|
@@ -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>>(
|
package/src/util/entity_cache.ts
CHANGED
|
@@ -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 {
|
|
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 {
|
|
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 {
|
|
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 {
|
|
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
|
-
|
|
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
|
-
}
|