@firecms/core 3.0.0-alpha.27 → 3.0.0-alpha.28
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/Popover.d.ts +3 -1
- package/dist/core/builders.d.ts +3 -3
- package/dist/core/components/EntityCollectionTable/filters/ReferenceFilterField.d.ts +3 -3
- package/dist/core/components/FieldConfigBadge.d.ts +3 -3
- package/dist/core/components/VirtualTable/VirtualTableHeader.d.ts +2 -2
- package/dist/core/form_field_configs.d.ts +3 -3
- package/dist/core/util/property_utils.d.ts +5 -5
- package/dist/core/util/references.d.ts +2 -2
- package/dist/core/util/resolutions.d.ts +5 -5
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/useBuildDataSource.d.ts +23 -0
- package/dist/index.es.js +6877 -6634
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +14 -14
- package/dist/index.umd.js.map +1 -1
- package/dist/types/datasource.d.ts +120 -11
- package/dist/types/fields.d.ts +1 -1
- package/dist/types/firecms.d.ts +3 -3
- package/dist/types/firecms_context.d.ts +3 -3
- package/dist/types/index.d.ts +1 -1
- package/dist/types/properties.d.ts +1 -1
- package/dist/types/{field_config.d.ts → property_config.d.ts} +3 -4
- package/package.json +2 -2
- package/src/components/Popover.tsx +10 -3
- package/src/components/TextareaAutosize.tsx +2 -1
- package/src/core/builders.ts +5 -5
- package/src/core/components/EntityCollectionTable/EntityCollectionTable.tsx +4 -4
- package/src/core/components/EntityCollectionTable/filters/ReferenceFilterField.tsx +11 -6
- package/src/core/components/EntityCollectionTable/internal/PropertyTableCell.tsx +2 -2
- package/src/core/components/EntityCollectionTable/internal/popup_field/PopupFormField.tsx +6 -2
- package/src/core/components/EntityCollectionView/EntityCollectionView.tsx +1 -1
- package/src/core/components/FieldConfigBadge.tsx +4 -4
- package/src/core/components/VirtualTable/VirtualTableHeader.tsx +21 -18
- package/src/core/form_field_configs.tsx +5 -5
- package/src/core/util/property_utils.tsx +5 -5
- package/src/core/util/references.ts +2 -2
- package/src/core/util/resolutions.ts +16 -11
- package/src/form/EntityForm.tsx +1 -1
- package/src/form/PropertyFieldBinding.tsx +4 -4
- package/src/form/field_bindings/KeyValueFieldBinding.tsx +1 -1
- package/src/form/field_bindings/ReadOnlyFieldBinding.tsx +4 -6
- package/src/hooks/index.tsx +1 -0
- package/src/hooks/useBuildDataSource.ts +351 -0
- package/src/preview/property_previews/StringPropertyPreview.tsx +4 -2
- package/src/types/datasource.ts +174 -10
- package/src/types/fields.tsx +1 -1
- package/src/types/firecms.tsx +3 -3
- package/src/types/firecms_context.tsx +3 -3
- package/src/types/index.ts +1 -1
- package/src/types/properties.ts +1 -1
- package/src/types/{field_config.tsx → property_config.tsx} +3 -4
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
|
|
3
|
-
import { EntityCollection,
|
|
3
|
+
import { EntityCollection, PropertyConfig, PropertiesOrBuilders, PropertyOrBuilder, ResolvedProperty } from "../../types";
|
|
4
4
|
import { getFieldConfig } from "../form_field_configs";
|
|
5
5
|
import { isPropertyBuilder } from "./entities";
|
|
6
6
|
import { resolveProperty } from "./resolutions";
|
|
7
7
|
import { CircleIcon, FunctionsIcon } from "../../icons";
|
|
8
8
|
|
|
9
9
|
export function isReferenceProperty(propertyOrBuilder: PropertyOrBuilder,
|
|
10
|
-
fields: Record<string,
|
|
10
|
+
fields: Record<string, PropertyConfig>) {
|
|
11
11
|
const resolvedProperty = resolveProperty({
|
|
12
12
|
propertyKey: "ignore", // TODO
|
|
13
13
|
propertyOrBuilder,
|
|
@@ -28,7 +28,7 @@ export function getIdIcon(size: "small" | "medium" | "large"): React.ReactNode {
|
|
|
28
28
|
return <CircleIcon size={size}/>;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
export function getIconForWidget(widget:
|
|
31
|
+
export function getIconForWidget(widget: PropertyConfig | undefined,
|
|
32
32
|
size: "small" | "medium" | "large") {
|
|
33
33
|
const Icon = widget?.Icon ?? CircleIcon;
|
|
34
34
|
return <Icon size={size}/>;
|
|
@@ -37,7 +37,7 @@ export function getIconForWidget(widget: FieldConfig | undefined,
|
|
|
37
37
|
export function getIconForProperty(
|
|
38
38
|
property: PropertyOrBuilder | ResolvedProperty,
|
|
39
39
|
size: "small" | "medium" | "large" = "small",
|
|
40
|
-
fields: Record<string,
|
|
40
|
+
fields: Record<string, PropertyConfig> = {}
|
|
41
41
|
): React.ReactNode {
|
|
42
42
|
|
|
43
43
|
if (isPropertyBuilder(property)) {
|
|
@@ -48,7 +48,7 @@ export function getIconForProperty(
|
|
|
48
48
|
}
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
export function getColorForProperty(property: PropertyOrBuilder, fields: Record<string,
|
|
51
|
+
export function getColorForProperty(property: PropertyOrBuilder, fields: Record<string, PropertyConfig>): string {
|
|
52
52
|
if (isPropertyBuilder(property)) {
|
|
53
53
|
return "#888";
|
|
54
54
|
} else {
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { EntityCollection,
|
|
1
|
+
import { EntityCollection, PropertyConfig } from "../../types";
|
|
2
2
|
import { isReferenceProperty } from "./property_utils";
|
|
3
3
|
import { isPropertyBuilder } from "./entities";
|
|
4
4
|
|
|
5
5
|
export function getReferencePreviewKeys(targetCollection: EntityCollection<any>,
|
|
6
|
-
fields: Record<string,
|
|
6
|
+
fields: Record<string, PropertyConfig>,
|
|
7
7
|
previewProperties?: string[],
|
|
8
8
|
limit = 3) {
|
|
9
9
|
const allProperties = Object.keys(targetCollection.properties);
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
2
|
ArrayProperty,
|
|
3
3
|
CMSType,
|
|
4
|
-
EntityCollection,
|
|
4
|
+
EntityCollection,
|
|
5
|
+
EntityCustomView,
|
|
5
6
|
EntityValues,
|
|
6
7
|
EnumValueConfig,
|
|
7
8
|
EnumValues,
|
|
8
|
-
|
|
9
|
+
PropertyConfig,
|
|
9
10
|
NumberProperty,
|
|
10
11
|
Properties,
|
|
11
12
|
PropertiesOrBuilders,
|
|
@@ -43,7 +44,7 @@ export const resolveCollection = <M extends Record<string, any>, >
|
|
|
43
44
|
values?: Partial<EntityValues<M>>,
|
|
44
45
|
previousValues?: Partial<EntityValues<M>>,
|
|
45
46
|
userConfigPersistence?: UserConfigurationPersistence;
|
|
46
|
-
fields?: Record<string,
|
|
47
|
+
fields?: Record<string, PropertyConfig>;
|
|
47
48
|
}): ResolvedEntityCollection<M> => {
|
|
48
49
|
|
|
49
50
|
const collectionOverride = userConfigPersistence?.getCollectionConfig<M>(path);
|
|
@@ -107,7 +108,7 @@ export function resolveProperty<T extends CMSType = CMSType, M extends Record<st
|
|
|
107
108
|
entityId?: string,
|
|
108
109
|
index?: number,
|
|
109
110
|
fromBuilder?: boolean;
|
|
110
|
-
fields?: Record<string,
|
|
111
|
+
fields?: Record<string, PropertyConfig<any>>;
|
|
111
112
|
}): ResolvedProperty<T> | null {
|
|
112
113
|
|
|
113
114
|
if (typeof propertyOrBuilder === "object" && "resolved" in propertyOrBuilder) {
|
|
@@ -176,17 +177,21 @@ export function resolveProperty<T extends CMSType = CMSType, M extends Record<st
|
|
|
176
177
|
} as ResolvedProperty<T>;
|
|
177
178
|
}
|
|
178
179
|
|
|
179
|
-
if (resolvedProperty.
|
|
180
|
+
if (resolvedProperty.propertyConfig && !isDefaultFieldConfigId(resolvedProperty.propertyConfig)) {
|
|
180
181
|
const cmsFields = props.fields;
|
|
181
182
|
if (!cmsFields) {
|
|
182
|
-
throw Error(`Trying to resolve a property with key ${resolvedProperty.
|
|
183
|
+
throw Error(`Trying to resolve a property with key ${resolvedProperty.propertyConfig} that inherits from a custom field but no custom fields were provided. Use the property 'fields' in your top level component to provide them`);
|
|
183
184
|
}
|
|
184
|
-
const customField:
|
|
185
|
+
const customField: PropertyConfig<any> = cmsFields[resolvedProperty.propertyConfig];
|
|
185
186
|
if (!customField)
|
|
186
|
-
throw Error(`Trying to resolve a property that inherits from a custom field but no custom field with id ${resolvedProperty.
|
|
187
|
+
throw Error(`Trying to resolve a property that inherits from a custom field but no custom field with id ${resolvedProperty.propertyConfig} was found. Check the \`fields\` in your top level component`);
|
|
187
188
|
if (customField.property) {
|
|
189
|
+
const configPropertyOrBuilder = customField.property;
|
|
190
|
+
if ("propertyConfig" in configPropertyOrBuilder) {
|
|
191
|
+
delete configPropertyOrBuilder.propertyConfig;
|
|
192
|
+
}
|
|
188
193
|
const customFieldProperty = resolveProperty<any>({
|
|
189
|
-
propertyOrBuilder:
|
|
194
|
+
propertyOrBuilder: configPropertyOrBuilder,
|
|
190
195
|
propertyValue,
|
|
191
196
|
...props
|
|
192
197
|
});
|
|
@@ -220,7 +225,7 @@ export function resolveArrayProperty<T extends any[], M>({
|
|
|
220
225
|
entityId?: string,
|
|
221
226
|
index?: number,
|
|
222
227
|
fromBuilder?: boolean;
|
|
223
|
-
fields?: Record<string,
|
|
228
|
+
fields?: Record<string, PropertyConfig>;
|
|
224
229
|
}): ResolvedArrayProperty {
|
|
225
230
|
|
|
226
231
|
if (property.of) {
|
|
@@ -326,7 +331,7 @@ export function resolveProperties<M extends Record<string, any>>({
|
|
|
326
331
|
entityId?: string,
|
|
327
332
|
index?: number,
|
|
328
333
|
fromBuilder?: boolean;
|
|
329
|
-
fields?: Record<string,
|
|
334
|
+
fields?: Record<string, PropertyConfig>;
|
|
330
335
|
}): ResolvedProperties<M> {
|
|
331
336
|
return Object.entries<PropertyOrBuilder>(properties as Record<string, PropertyOrBuilder>)
|
|
332
337
|
.map(([key, property]) => {
|
package/src/form/EntityForm.tsx
CHANGED
|
@@ -315,7 +315,7 @@ function EntityFormInternal<M extends Record<string, any>>({
|
|
|
315
315
|
name,
|
|
316
316
|
value,
|
|
317
317
|
property
|
|
318
|
-
}) => dataSource.checkUniqueField(path, name, value,
|
|
318
|
+
}) => dataSource.checkUniqueField(path, name, value, entityId),
|
|
319
319
|
[dataSource, path, entityId]);
|
|
320
320
|
|
|
321
321
|
const validationSchema = useMemo(() => entityId
|
|
@@ -121,13 +121,13 @@ function PropertyFieldBindingInternal<T extends CMSType = CMSType, CustomProps =
|
|
|
121
121
|
Component = resolvedProperty.Field as ComponentType<FieldProps<any>>;
|
|
122
122
|
}
|
|
123
123
|
} else {
|
|
124
|
-
const
|
|
125
|
-
if (!
|
|
126
|
-
console.log("INTERNAL: Could not find field config for property", { propertyKey, resolvedProperty, fields: fireCMSContext.fields,
|
|
124
|
+
const propertyConfig = getFieldConfig(resolvedProperty, fireCMSContext.fields);
|
|
125
|
+
if (!propertyConfig) {
|
|
126
|
+
console.log("INTERNAL: Could not find field config for property", { propertyKey, resolvedProperty, fields: fireCMSContext.fields, propertyConfig });
|
|
127
127
|
throw new Error(`INTERNAL: Could not find field config for property ${propertyKey}`);
|
|
128
128
|
}
|
|
129
129
|
const configProperty = resolveProperty({
|
|
130
|
-
propertyOrBuilder:
|
|
130
|
+
propertyOrBuilder: propertyConfig.property,
|
|
131
131
|
propertyValue: fieldProps.field.value,
|
|
132
132
|
values: fieldProps.form.values,
|
|
133
133
|
path: context.path,
|
|
@@ -164,7 +164,7 @@ function MapEditView<T extends Record<string, any>>({
|
|
|
164
164
|
}
|
|
165
165
|
|
|
166
166
|
const newValue = { ...(value ?? {}) } as T;
|
|
167
|
-
if (originalValue.current && fieldKey in originalValue.current) {
|
|
167
|
+
if (typeof originalValue.current === "object" && fieldKey in originalValue.current) {
|
|
168
168
|
// @ts-ignore
|
|
169
169
|
newValue[fieldKey] = undefined; // set to undefined to remove from the object, the datasource will remove it from the backend
|
|
170
170
|
} else {
|
|
@@ -3,12 +3,10 @@ import React from "react";
|
|
|
3
3
|
import { Entity, FieldProps } from "../../types";
|
|
4
4
|
|
|
5
5
|
import { PropertyPreview } from "../../preview";
|
|
6
|
-
import { LabelWithIcon } from "../components";
|
|
7
|
-
import { ErrorBoundary } from "../../core
|
|
8
|
-
import { getIconForProperty } from "../../core";
|
|
6
|
+
import { FieldHelperText, LabelWithIcon } from "../components";
|
|
7
|
+
import { ErrorBoundary, getIconForProperty } from "../../core";
|
|
9
8
|
import { paperMixin } from "../../styles";
|
|
10
|
-
import {
|
|
11
|
-
import { cn } from "../../components/util/cn";
|
|
9
|
+
import { cn } from "../../components";
|
|
12
10
|
|
|
13
11
|
/**
|
|
14
12
|
*
|
|
@@ -49,7 +47,7 @@ export function ReadOnlyFieldBinding({
|
|
|
49
47
|
}
|
|
50
48
|
|
|
51
49
|
<div
|
|
52
|
-
className={cn(paperMixin, "min-h-14 p-4 md:p-6")}>
|
|
50
|
+
className={cn(paperMixin, "min-h-14 p-4 md:p-6 overflow-x-scroll no-scrollbar")}>
|
|
53
51
|
|
|
54
52
|
<ErrorBoundary>
|
|
55
53
|
<PropertyPreview propertyKey={propertyKey}
|
package/src/hooks/index.tsx
CHANGED
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
import { useCallback } from "react";
|
|
2
|
+
import {
|
|
3
|
+
DataSource,
|
|
4
|
+
DataSourceDelegate, DeleteEntityProps,
|
|
5
|
+
Entity,
|
|
6
|
+
EntityCollection,
|
|
7
|
+
EntityReference,
|
|
8
|
+
EntityValues,
|
|
9
|
+
FetchCollectionProps,
|
|
10
|
+
FetchEntityProps,
|
|
11
|
+
FilterValues,
|
|
12
|
+
GeoPoint,
|
|
13
|
+
ListenCollectionProps,
|
|
14
|
+
ListenEntityProps,
|
|
15
|
+
PropertyConfig,
|
|
16
|
+
ResolvedProperties,
|
|
17
|
+
SaveEntityProps
|
|
18
|
+
} from "../types";
|
|
19
|
+
import { resolveCollection, updateDateAutoValues } from "../core";
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Use this hook to build a {@link DataSource} based on Firestore
|
|
23
|
+
* @param firebaseApp
|
|
24
|
+
* @param textSearchController
|
|
25
|
+
* @param collectionRegistry
|
|
26
|
+
* @category Firebase
|
|
27
|
+
*/
|
|
28
|
+
export function useBuildDataSource({
|
|
29
|
+
delegate,
|
|
30
|
+
customFields
|
|
31
|
+
}: {
|
|
32
|
+
delegate: DataSourceDelegate,
|
|
33
|
+
customFields: Record<string, PropertyConfig>;
|
|
34
|
+
}): DataSource {
|
|
35
|
+
|
|
36
|
+
return {
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Fetch entities in a Firestore path
|
|
40
|
+
* @param path
|
|
41
|
+
* @param collection
|
|
42
|
+
* @param filter
|
|
43
|
+
* @param limit
|
|
44
|
+
* @param startAfter
|
|
45
|
+
* @param searchString
|
|
46
|
+
* @param orderBy
|
|
47
|
+
* @param order
|
|
48
|
+
* @return Function to cancel subscription
|
|
49
|
+
* @see useCollectionFetch if you need this functionality implemented as a hook
|
|
50
|
+
* @category Firestore
|
|
51
|
+
*/
|
|
52
|
+
fetchCollection: useCallback(<M extends Record<string, any>>({
|
|
53
|
+
path,
|
|
54
|
+
collection,
|
|
55
|
+
filter,
|
|
56
|
+
limit,
|
|
57
|
+
startAfter,
|
|
58
|
+
searchString,
|
|
59
|
+
orderBy,
|
|
60
|
+
order
|
|
61
|
+
}: FetchCollectionProps<M>
|
|
62
|
+
): Promise<Entity<M>[]> => {
|
|
63
|
+
|
|
64
|
+
return delegate.fetchCollection<M>({
|
|
65
|
+
path,
|
|
66
|
+
filter,
|
|
67
|
+
limit,
|
|
68
|
+
startAfter,
|
|
69
|
+
searchString,
|
|
70
|
+
orderBy,
|
|
71
|
+
order
|
|
72
|
+
});
|
|
73
|
+
}, [delegate]),
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Listen to a entities in a given path
|
|
77
|
+
* @param path
|
|
78
|
+
* @param collection
|
|
79
|
+
* @param onError
|
|
80
|
+
* @param filter
|
|
81
|
+
* @param limit
|
|
82
|
+
* @param startAfter
|
|
83
|
+
* @param searchString
|
|
84
|
+
* @param orderBy
|
|
85
|
+
* @param order
|
|
86
|
+
* @param onUpdate
|
|
87
|
+
* @return Function to cancel subscription
|
|
88
|
+
* @see useCollectionFetch if you need this functionality implemented as a hook
|
|
89
|
+
* @category Firestore
|
|
90
|
+
*/
|
|
91
|
+
listenCollection: delegate.listenCollection
|
|
92
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
93
|
+
? useCallback(<M extends Record<string, any>>(
|
|
94
|
+
{
|
|
95
|
+
path,
|
|
96
|
+
collection,
|
|
97
|
+
filter,
|
|
98
|
+
limit,
|
|
99
|
+
startAfter,
|
|
100
|
+
searchString,
|
|
101
|
+
orderBy,
|
|
102
|
+
order,
|
|
103
|
+
onUpdate,
|
|
104
|
+
onError
|
|
105
|
+
}: ListenCollectionProps<M>
|
|
106
|
+
): () => void => {
|
|
107
|
+
|
|
108
|
+
const isCollectionGroup = Boolean(collection.collectionGroup) ?? false;
|
|
109
|
+
if (!delegate.listenCollection)
|
|
110
|
+
throw Error("useBuildDataSource delegate not initialised");
|
|
111
|
+
|
|
112
|
+
return delegate.listenCollection<M>({
|
|
113
|
+
path,
|
|
114
|
+
filter,
|
|
115
|
+
limit,
|
|
116
|
+
startAfter,
|
|
117
|
+
searchString,
|
|
118
|
+
orderBy,
|
|
119
|
+
order,
|
|
120
|
+
onUpdate,
|
|
121
|
+
onError,
|
|
122
|
+
isCollectionGroup
|
|
123
|
+
});
|
|
124
|
+
}, [delegate])
|
|
125
|
+
: undefined,
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Retrieve an entity given a path and a collection
|
|
129
|
+
* @param path
|
|
130
|
+
* @param entityId
|
|
131
|
+
* @param collection
|
|
132
|
+
* @category Firestore
|
|
133
|
+
*/
|
|
134
|
+
fetchEntity: useCallback(<M extends Record<string, any>>({
|
|
135
|
+
path,
|
|
136
|
+
entityId
|
|
137
|
+
}: FetchEntityProps<M>
|
|
138
|
+
): Promise<Entity<M> | undefined> => delegate.fetchEntity({
|
|
139
|
+
path,
|
|
140
|
+
entityId
|
|
141
|
+
}), [delegate]),
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
*
|
|
145
|
+
* @param path
|
|
146
|
+
* @param entityId
|
|
147
|
+
* @param collection
|
|
148
|
+
* @param onUpdate
|
|
149
|
+
* @param onError
|
|
150
|
+
* @return Function to cancel subscription
|
|
151
|
+
* @category Firestore
|
|
152
|
+
*/
|
|
153
|
+
listenEntity: delegate.listenEntity
|
|
154
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
155
|
+
? useCallback(<M extends Record<string, any>>(
|
|
156
|
+
{
|
|
157
|
+
path,
|
|
158
|
+
entityId,
|
|
159
|
+
collection,
|
|
160
|
+
onUpdate,
|
|
161
|
+
onError
|
|
162
|
+
}: ListenEntityProps<M>): () => void => {
|
|
163
|
+
if (!delegate.listenEntity)
|
|
164
|
+
throw Error("useBuildDataSource delegate not initialised");
|
|
165
|
+
|
|
166
|
+
return delegate.listenEntity<M>({
|
|
167
|
+
path,
|
|
168
|
+
entityId,
|
|
169
|
+
onUpdate,
|
|
170
|
+
onError
|
|
171
|
+
})
|
|
172
|
+
}, [delegate.listenEntity]) : undefined,
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Save entity to the specified path. Note that Firestore does not allow
|
|
176
|
+
* undefined values.
|
|
177
|
+
* @param path
|
|
178
|
+
* @param entityId
|
|
179
|
+
* @param values
|
|
180
|
+
* @param schemaId
|
|
181
|
+
* @param collection
|
|
182
|
+
* @param status
|
|
183
|
+
* @category Firestore
|
|
184
|
+
*/
|
|
185
|
+
saveEntity: useCallback(<M extends Record<string, any>>(
|
|
186
|
+
{
|
|
187
|
+
path,
|
|
188
|
+
entityId,
|
|
189
|
+
values,
|
|
190
|
+
collection,
|
|
191
|
+
status
|
|
192
|
+
}: SaveEntityProps<M>): Promise<Entity<M>> => {
|
|
193
|
+
|
|
194
|
+
const resolvedCollection = resolveCollection<M>({
|
|
195
|
+
collection,
|
|
196
|
+
path,
|
|
197
|
+
entityId,
|
|
198
|
+
fields: customFields
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
const properties: ResolvedProperties<M> = resolvedCollection.properties;
|
|
202
|
+
|
|
203
|
+
const firestoreValues = cmsToDelegateModel(
|
|
204
|
+
values,
|
|
205
|
+
delegate.buildReference,
|
|
206
|
+
delegate.buildGeoPoint,
|
|
207
|
+
delegate.buildDate,
|
|
208
|
+
delegate.buildDeleteFieldValue
|
|
209
|
+
);
|
|
210
|
+
const updatedFirestoreValues: EntityValues<M> = updateDateAutoValues(
|
|
211
|
+
{
|
|
212
|
+
inputValues: firestoreValues,
|
|
213
|
+
properties,
|
|
214
|
+
status,
|
|
215
|
+
timestampNowValue: delegate.currentTime(),
|
|
216
|
+
setDateToMidnight: delegate.setDateToMidnight
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
console.debug("Saving entity", path, entityId, updatedFirestoreValues);
|
|
220
|
+
|
|
221
|
+
return delegate.saveEntity({
|
|
222
|
+
path,
|
|
223
|
+
entityId,
|
|
224
|
+
values: updatedFirestoreValues,
|
|
225
|
+
status
|
|
226
|
+
}).then((res) => {
|
|
227
|
+
return {
|
|
228
|
+
id: res.id,
|
|
229
|
+
path: res.path,
|
|
230
|
+
values: delegate.delegateToCMSModel(updatedFirestoreValues)
|
|
231
|
+
} as Entity<M>;
|
|
232
|
+
});
|
|
233
|
+
}, [delegate.saveEntity]),
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Delete an entity
|
|
237
|
+
* @param entity
|
|
238
|
+
* @param collection
|
|
239
|
+
* @category Firestore
|
|
240
|
+
*/
|
|
241
|
+
deleteEntity: useCallback(<M extends Record<string, any>>(
|
|
242
|
+
{
|
|
243
|
+
entity
|
|
244
|
+
}: DeleteEntityProps<M>
|
|
245
|
+
): Promise<void> => {
|
|
246
|
+
return delegate.deleteEntity({entity});
|
|
247
|
+
}, [delegate.deleteEntity]),
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Check if the given property is unique in the given collection
|
|
251
|
+
* @param path Collection path
|
|
252
|
+
* @param name of the property
|
|
253
|
+
* @param value
|
|
254
|
+
* @param property
|
|
255
|
+
* @param entityId
|
|
256
|
+
* @return `true` if there are no other fields besides the given entity
|
|
257
|
+
* @category Firestore
|
|
258
|
+
*/
|
|
259
|
+
checkUniqueField: useCallback((
|
|
260
|
+
path: string,
|
|
261
|
+
name: string,
|
|
262
|
+
value: any,
|
|
263
|
+
entityId?: string
|
|
264
|
+
): Promise<boolean> => {
|
|
265
|
+
return delegate.checkUniqueField(path, name, value, entityId);
|
|
266
|
+
}, [delegate.checkUniqueField]),
|
|
267
|
+
|
|
268
|
+
generateEntityId: useCallback((path: string): string => {
|
|
269
|
+
return delegate.generateEntityId(path, );
|
|
270
|
+
}, [delegate.generateEntityId]),
|
|
271
|
+
|
|
272
|
+
countEntities: useCallback(async ({
|
|
273
|
+
path,
|
|
274
|
+
collection,
|
|
275
|
+
filter,
|
|
276
|
+
order,
|
|
277
|
+
orderBy
|
|
278
|
+
}: {
|
|
279
|
+
path: string,
|
|
280
|
+
collection: EntityCollection<any>,
|
|
281
|
+
filter?: FilterValues<Extract<keyof any, string>>,
|
|
282
|
+
orderBy?: string,
|
|
283
|
+
order?: "desc" | "asc",
|
|
284
|
+
}): Promise<number> => {
|
|
285
|
+
return delegate.countEntities({
|
|
286
|
+
path,
|
|
287
|
+
filter,
|
|
288
|
+
orderBy,
|
|
289
|
+
order,
|
|
290
|
+
isCollectionGroup: Boolean(collection.collectionGroup) ?? false
|
|
291
|
+
});
|
|
292
|
+
}, [delegate.countEntities]),
|
|
293
|
+
|
|
294
|
+
isFilterCombinationValid: useCallback(({
|
|
295
|
+
path,
|
|
296
|
+
filterValues,
|
|
297
|
+
sortBy
|
|
298
|
+
}: {
|
|
299
|
+
path: string,
|
|
300
|
+
filterValues: FilterValues<any>,
|
|
301
|
+
sortBy?: [string, "asc" | "desc"]
|
|
302
|
+
}): boolean => {
|
|
303
|
+
if (!delegate.isFilterCombinationValid)
|
|
304
|
+
return true;
|
|
305
|
+
return delegate.isFilterCombinationValid(
|
|
306
|
+
{
|
|
307
|
+
path,
|
|
308
|
+
filterValues,
|
|
309
|
+
sortBy
|
|
310
|
+
}
|
|
311
|
+
)
|
|
312
|
+
}, [delegate.isFilterCombinationValid])
|
|
313
|
+
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Recursive function that converts Firestore data types into CMS or plain
|
|
320
|
+
* JS types.
|
|
321
|
+
* FireCMS uses Javascript dates internally instead of Firestore timestamps.
|
|
322
|
+
* This makes it easier to interact with the rest of the libraries and
|
|
323
|
+
* bindings.
|
|
324
|
+
* Also, Firestore references are replaced with {@link EntityReference}
|
|
325
|
+
* @param data
|
|
326
|
+
* @category Firestore
|
|
327
|
+
*/
|
|
328
|
+
export function cmsToDelegateModel(data: any,
|
|
329
|
+
buildReference: (reference: EntityReference) => any,
|
|
330
|
+
buildGeoPoint: (geoPoint: GeoPoint) => any,
|
|
331
|
+
buildDate: (date: Date) => any,
|
|
332
|
+
buildDelete: () => any
|
|
333
|
+
): any {
|
|
334
|
+
if (data === undefined) {
|
|
335
|
+
return buildDelete();
|
|
336
|
+
} else if (Array.isArray(data)) {
|
|
337
|
+
return data.map(v => cmsToDelegateModel(v, buildReference, buildGeoPoint, buildDate, buildDelete));
|
|
338
|
+
} else if (data instanceof EntityReference) {
|
|
339
|
+
return buildReference(data);
|
|
340
|
+
} else if (data instanceof GeoPoint) {
|
|
341
|
+
return buildGeoPoint(data);
|
|
342
|
+
} else if (data instanceof Date) {
|
|
343
|
+
return buildDate(data);
|
|
344
|
+
} else if (data && typeof data === "object") {
|
|
345
|
+
return Object.entries(data)
|
|
346
|
+
.map(([key, v]) => ({ [key]: cmsToDelegateModel(v, buildReference, buildGeoPoint, buildDate, buildDelete) }))
|
|
347
|
+
.reduce((a, b) => ({ ...a, ...b }), {});
|
|
348
|
+
}
|
|
349
|
+
return data;
|
|
350
|
+
}
|
|
351
|
+
|
|
@@ -45,13 +45,15 @@ export function StringPropertyPreview({
|
|
|
45
45
|
if (!value) return <></>;
|
|
46
46
|
const lines = value.split("\n");
|
|
47
47
|
return value && value.includes("\n")
|
|
48
|
-
? <div>
|
|
48
|
+
? <div className={"overflow-x-scroll"}>
|
|
49
49
|
{lines.map((str, index) =>
|
|
50
50
|
<React.Fragment key={`string_preview_${index}`}>
|
|
51
51
|
<span>{str}</span>
|
|
52
52
|
{index !== lines.length - 1 && <br/>}
|
|
53
53
|
</React.Fragment>)}
|
|
54
54
|
</div>
|
|
55
|
-
: <>
|
|
55
|
+
: <>
|
|
56
|
+
{value}
|
|
57
|
+
</>;
|
|
56
58
|
}
|
|
57
59
|
}
|