@firecms/core 3.0.0-alpha.58 → 3.0.0-alpha.60

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.
@@ -31,7 +31,7 @@ export type EntityValues<M extends object> = M;
31
31
  /**
32
32
  * Class used to create a reference to an entity in a different path
33
33
  */
34
- export declare class EntityReference {
34
+ export declare class EntityReference<M extends Record<string, any> = any> {
35
35
  /**
36
36
  * ID of the entity
37
37
  */
@@ -23,6 +23,6 @@ export declare function updateDateAutoValues<M extends Record<string, any>>({ in
23
23
  * @group Datasource
24
24
  */
25
25
  export declare function sanitizeData<M extends Record<string, any>>(values: EntityValues<M>, properties: ResolvedProperties<M>): any;
26
- export declare function getReferenceFrom(entity: Entity<any>): EntityReference;
26
+ export declare function getReferenceFrom<M extends Record<string, any>>(entity: Entity<M>): EntityReference<M>;
27
27
  export declare function traverseValuesProperties<M extends Record<string, any>>(inputValues: Partial<EntityValues<M>>, properties: ResolvedProperties<M>, operation: (value: any, property: Property) => any): EntityValues<M> | undefined;
28
28
  export declare function traverseValueProperty(inputValue: any, property: Property, operation: (value: any, property: Property) => any): any;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@firecms/core",
3
- "version": "3.0.0-alpha.58",
3
+ "version": "3.0.0-alpha.60",
4
4
  "description": "Awesome Firebase/Firestore-based headless open-source CMS",
5
5
  "funding": {
6
6
  "url": "https://github.com/sponsors/firecmsco"
@@ -45,7 +45,7 @@
45
45
  "generateIcons": "ts-node --esm src/icons/generateIcons.ts"
46
46
  },
47
47
  "dependencies": {
48
- "@firecms/ui": "^3.0.0-alpha.58",
48
+ "@firecms/ui": "^3.0.0-alpha.60",
49
49
  "@fontsource/ibm-plex-mono": "^5.0.8",
50
50
  "@fontsource/roboto": "^5.0.8",
51
51
  "@hello-pangea/dnd": "^16.5.0",
@@ -116,7 +116,7 @@
116
116
  "dist",
117
117
  "src"
118
118
  ],
119
- "gitHead": "64c77fec3109f226216fe452ab828c9fcca6f7c8",
119
+ "gitHead": "04c920a874385714dd35f320256e6def346c2a9e",
120
120
  "publishConfig": {
121
121
  "access": "public"
122
122
  }
@@ -1,64 +1,52 @@
1
1
  import React, { useCallback, useMemo } from "react";
2
2
 
3
- import { Entity, EntityCollection, EntityReference, FilterValues, ResolvedProperty } from "../types";
4
- import { getReferenceFrom, getReferencePreviewKeys } from "../util";
5
- import { PropertyPreview, SkeletonPropertyComponent } from "../preview";
6
- import {
7
- useEntityFetch,
8
- useFireCMSContext,
9
- useNavigationController,
10
- useReferenceDialog,
11
- useSideEntityController
12
- } from "../hooks";
13
- import { LabelWithIcon } from "../components";
14
- import {
15
- Button,
16
- ClearIcon,
17
- cn,
18
- ErrorIcon,
19
- IconButton,
20
- KeyboardTabIcon,
21
- LinkIcon,
22
- Tooltip,
23
- Typography
24
- } from "@firecms/ui";
25
- import { ErrorBoundary, ErrorView } from "../components";
3
+ import { Entity, EntityCollection, EntityReference, FilterValues } from "../types";
4
+ import { getReferenceFrom } from "../util";
5
+ import { PreviewSize, ReferencePreview } from "../preview";
6
+ import { useNavigationController, useReferenceDialog } from "../hooks";
7
+ import { Button, cn } from "@firecms/ui";
8
+
9
+ export type ReferenceWidgetProps<M extends Record<string, any>> = {
10
+ name?: string,
11
+ multiselect?: boolean,
12
+ value: EntityReference<M> | EntityReference<M>[] | null,
13
+ onReferenceSelected?: (params: {
14
+ reference: EntityReference<M> | null,
15
+ entity: Entity<M> | null
16
+ }) => void,
17
+ onMultipleReferenceSelected?: (params: {
18
+ references: EntityReference<M>[] | null,
19
+ entities: Entity<M>[] | null
20
+ }) => void,
21
+ path: string,
22
+ disabled?: boolean,
23
+ previewProperties?: string[];
24
+ /**
25
+ * Allow selection of entities that pass the given filter only.
26
+ */
27
+ forceFilter?: FilterValues<string>;
28
+ size: PreviewSize;
29
+ className?: string;
30
+ };
26
31
 
27
32
  /**
28
33
  * This field allows selecting reference/s.
29
- * @param name
30
- * @param path
31
- * @param disabled
32
- * @param value
33
- * @param setValue
34
- * @param previewProperties
35
- * @param forceFilter
36
- * @constructor
37
34
  */
38
35
  export function ReferenceWidget<M extends Record<string, any>>({
39
36
  name,
37
+ multiselect = false,
40
38
  path,
41
39
  disabled,
42
40
  value,
43
- setValue,
41
+ onReferenceSelected,
42
+ onMultipleReferenceSelected,
44
43
  previewProperties,
45
- forceFilter
46
- }: {
47
- name?: string,
48
- value?: EntityReference,
49
- setValue: (value: EntityReference | null) => void,
50
- path: string,
51
- disabled?: boolean,
52
- previewProperties?: string[];
53
- /**
54
- * Allow selection of entities that pass the given filter only.
55
- */
56
- forceFilter?: FilterValues<string>;
57
- }) {
44
+ forceFilter,
45
+ size,
46
+ className
47
+ }: ReferenceWidgetProps<M>) {
58
48
 
59
- const fireCMSContext = useFireCMSContext();
60
49
  const navigationController = useNavigationController();
61
- const sideEntityController = useSideEntityController();
62
50
 
63
51
  const collection: EntityCollection | undefined = useMemo(() => {
64
52
  return navigationController.getCollection(path);
@@ -68,188 +56,90 @@ export function ReferenceWidget<M extends Record<string, any>>({
68
56
  throw Error(`Couldn't find the corresponding collection for the path: ${path}`);
69
57
  }
70
58
 
71
- const validValue = value && value instanceof EntityReference;
72
-
73
- const {
74
- entity,
75
- dataLoading,
76
- dataLoadingError
77
- } = useEntityFetch({
78
- path,
79
- entityId: validValue ? value.id : undefined,
80
- collection,
81
- useCache: true
82
- });
59
+ const onSingleEntitySelected = useCallback((entity: Entity<M> | null) => {
60
+ if (disabled)
61
+ return;
62
+ if (onReferenceSelected) {
63
+ const reference = entity ? getReferenceFrom(entity) : null;
64
+ onReferenceSelected?.({ reference, entity });
65
+ }
66
+ }, [disabled, onReferenceSelected]);
83
67
 
84
- const onSingleEntitySelected = useCallback((entity: Entity<M>) => {
68
+ const onMultipleEntitiesSelected = useCallback((entities: Entity<M>[]) => {
85
69
  if (disabled)
86
70
  return;
87
- setValue(entity ? getReferenceFrom(entity) : null);
88
- }, [disabled, setValue]);
71
+ if (onMultipleReferenceSelected) {
72
+ const references = entities ? entities.map(e => getReferenceFrom(e)) : null;
73
+ onMultipleReferenceSelected({ references, entities });
74
+ }
75
+ }, [disabled, onReferenceSelected]);
89
76
 
90
77
  const referenceDialogController = useReferenceDialog({
91
- multiselect: false,
78
+ multiselect,
92
79
  path,
93
80
  collection,
94
81
  onSingleEntitySelected,
82
+ onMultipleEntitiesSelected,
95
83
  forceFilter
96
84
  }
97
85
  );
98
86
 
99
- const handleClickOpen = useCallback(() => {
100
- referenceDialogController.open();
101
- }, [referenceDialogController]);
102
-
103
87
  const clearValue = useCallback((e: React.MouseEvent) => {
104
88
  e.stopPropagation();
105
- setValue(null);
106
- }, [setValue]);
107
-
108
- const seeEntityDetails = useCallback((e: React.MouseEvent) => {
109
- e.stopPropagation();
110
- if (entity) {
111
- fireCMSContext.onAnalyticsEvent?.("entity_click_from_reference", {
112
- path: entity.path,
113
- entityId: entity.id
114
- });
115
- sideEntityController.open({
116
- entityId: entity.id,
117
- path,
118
- updateUrl: true
119
- });
120
- }
121
- }, [entity, path, sideEntityController]);
122
-
123
- const buildEntityView = (collection?: EntityCollection<any>) => {
124
-
125
- const missingEntity = entity && !entity.values;
126
-
127
- let body: React.ReactNode;
128
- if (!collection) {
129
- body = (
130
- <ErrorView
131
- error={"The specified collection does not exist. Check console"}/>
132
- );
133
- } else if (missingEntity) {
134
- body = (
135
- <Tooltip title={value && value.path}>
136
- <div className="flex items-center p-4 flex-grow">
137
- <ErrorIcon size={"small"} color={"error"}/>
138
- <div className="ml-4">Missing
139
- reference {entity && entity.id}</div>
140
- </div>
141
- </Tooltip>
142
- );
89
+ if (multiselect) {
90
+ onMultipleEntitiesSelected([]);
143
91
  } else {
144
- if (validValue) {
145
-
146
- const listProperties = getReferencePreviewKeys(collection, fireCMSContext.propertyConfigs, previewProperties, 3);
147
-
148
- body = (
149
- <div className="flex flex-col flex-grow ml-4 mr-4">
150
-
151
- {listProperties && listProperties.map((key) => {
152
- const property = collection.properties[key as string];
153
- if (!property) return null;
154
- return (
155
- <div
156
- key={`reference_previews_${key as string}`}
157
- className="mt-1 mb-1">
158
- <ErrorBoundary>{
159
- entity
160
- ? <PropertyPreview
161
- propertyKey={key as string}
162
- value={(entity.values)[key]}
163
- property={property as ResolvedProperty}
164
- entity={entity}
165
- size={"tiny"}/>
166
- : <SkeletonPropertyComponent
167
- property={property as ResolvedProperty}
168
- size={"tiny"}/>}
169
- </ErrorBoundary>
170
- </div>
171
- );
172
- })}
173
- </div>
174
- );
175
- } else {
176
- body = <div className="p-4 flex justify-center"
177
- onClick={disabled ? undefined : handleClickOpen}>
178
- <Typography variant={"label"}
179
- className="flex-grow text-center">No value
180
- set</Typography>
181
- {!disabled && <Button variant="outlined"
182
- color="primary">
183
- Set
184
- </Button>}
185
- </div>;
186
- }
92
+ onSingleEntitySelected(null);
187
93
  }
94
+ }, [onReferenceSelected]);
95
+
96
+ let child: React.ReactNode;
188
97
 
189
- return (
190
- <div
191
- onClick={disabled ? undefined : handleClickOpen}
192
- className="flex">
193
-
194
- <div className="flex flex-col flex-grow">
195
-
196
- <div className="flex flex-col flex-grow">
197
- <LabelWithIcon icon={<LinkIcon color={"inherit"}
198
- />}
199
- className="text-text-secondary dark:text-text-secondary-dark ml-3.5"
200
- title={name}
201
- />
202
-
203
- {entity &&
204
- <div className="self-center m-4">
205
- <Tooltip title={value && value.path}>
206
- <Typography variant={"caption"}
207
- className={"font-mono"}>
208
- {entity.id}
209
- </Typography>
210
- </Tooltip>
211
- </div>}
212
-
213
- {!missingEntity && entity && value &&
214
- <Tooltip title={`See details for ${entity.id}`}>
215
- <span>
216
- <IconButton
217
- onClick={seeEntityDetails}
218
- size="large">
219
- <KeyboardTabIcon/>
220
- </IconButton>
221
- </span>
222
- </Tooltip>}
223
-
224
- {value && <div>
225
- <Tooltip title=" Clear">
226
- <span>
227
- <IconButton
228
- disabled={disabled}
229
- onClick={disabled ? undefined : clearValue}
230
- size="large">
231
- <ClearIcon/>
232
- </IconButton>
233
- </span>
234
- </Tooltip>
235
- </div>}
236
-
237
- </div>
238
-
239
- {body}
240
-
241
- </div>
242
- </div>
243
- );
98
+ const onEntryClick = () => {
99
+ if (disabled)
100
+ return;
101
+ referenceDialogController.open();
244
102
  };
245
103
 
246
- return <Typography variant={"label"}
247
- className={cn("relative w-full transition-colors duration-200 ease-in border rounded font-medium",
248
- disabled ? "bg-opacity-50" : "hover:bg-opacity-75",
249
- "text-opacity-50 dark:text-white dark:text-opacity-50")}
250
- >
104
+ if (Array.isArray(value)) {
105
+ child = <div className={"flex flex-col gap-4"}>
106
+ {value.map((ref, index) => {
107
+ return <ReferencePreview
108
+ key={`reference_preview_${index}`}
109
+ onClick={onEntryClick}
110
+ reference={ref}
111
+ disabled={disabled}
112
+ previewProperties={previewProperties}
113
+ size={size}/>
114
+ })}
115
+ </div>
116
+ } else if (value instanceof EntityReference) {
117
+ child = <ReferencePreview
118
+ reference={value}
119
+ onClick={onEntryClick}
120
+ disabled={disabled}
121
+ previewProperties={previewProperties}
122
+ size={size}/>
251
123
 
252
- {collection && buildEntityView(collection)}
124
+ }
125
+ return <div className={cn("text-sm font-medium text-gray-500",
126
+ "min-w-80 flex flex-col gap-4",
127
+ "relative transition-colors duration-200 ease-in rounded font-medium",
128
+ disabled ? "bg-opacity-50" : "hover:bg-opacity-75",
129
+ "text-opacity-50 dark:text-white dark:text-opacity-50",
130
+ className
131
+ )}
132
+ >
253
133
 
254
- </Typography>;
134
+ {child}
135
+ {!value && <div className="justify-center text-left">
136
+ <Button variant="outlined"
137
+ color="primary"
138
+ disabled={disabled}
139
+ onClick={onEntryClick}>
140
+ Edit {name}
141
+ </Button>
142
+ </div>}
143
+
144
+ </div>;
255
145
  }
@@ -37,7 +37,7 @@ export type EntityValues<M extends object> = M;
37
37
  /**
38
38
  * Class used to create a reference to an entity in a different path
39
39
  */
40
- export class EntityReference {
40
+ export class EntityReference<M extends Record<string, any> = any> {
41
41
  /**
42
42
  * ID of the entity
43
43
  */
@@ -139,7 +139,7 @@ export function sanitizeData<M extends Record<string, any>>
139
139
  return result;
140
140
  }
141
141
 
142
- export function getReferenceFrom(entity: Entity<any>): EntityReference {
142
+ export function getReferenceFrom<M extends Record<string, any>>(entity: Entity<M>): EntityReference<M> {
143
143
  return new EntityReference(entity.id, entity.path);
144
144
  }
145
145