@firecms/collection_editor 3.0.0-alpha.17 → 3.0.0-alpha.19

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 (64) hide show
  1. package/package.json +4 -3
  2. package/src/ConfigControllerProvider.tsx +177 -0
  3. package/src/components/EditorCollectionAction.tsx +95 -0
  4. package/src/components/HomePageEditorCollectionAction.tsx +81 -0
  5. package/src/components/NewCollectionCard.tsx +45 -0
  6. package/src/components/RootCollectionSuggestions.tsx +53 -0
  7. package/src/components/collection_editor/CollectionDetailsForm.tsx +312 -0
  8. package/src/components/collection_editor/CollectionEditorDialog.tsx +640 -0
  9. package/src/components/collection_editor/CollectionEditorWelcomeView.tsx +212 -0
  10. package/src/components/collection_editor/CollectionPropertiesEditorForm.tsx +450 -0
  11. package/src/components/collection_editor/CollectionYupValidation.tsx +6 -0
  12. package/src/components/collection_editor/EntityCustomViewsSelectDialog.tsx +29 -0
  13. package/src/components/collection_editor/EnumForm.tsx +354 -0
  14. package/src/components/collection_editor/PropertyEditView.tsx +535 -0
  15. package/src/components/collection_editor/PropertyFieldPreview.tsx +205 -0
  16. package/src/components/collection_editor/PropertySelectItem.tsx +31 -0
  17. package/src/components/collection_editor/PropertyTree.tsx +228 -0
  18. package/src/components/collection_editor/SelectIcons.tsx +72 -0
  19. package/src/components/collection_editor/SubcollectionsEditTab.tsx +239 -0
  20. package/src/components/collection_editor/UnsavedChangesDialog.tsx +47 -0
  21. package/src/components/collection_editor/import/CollectionEditorImportDataPreview.tsx +37 -0
  22. package/src/components/collection_editor/import/CollectionEditorImportMapping.tsx +236 -0
  23. package/src/components/collection_editor/import/clean_import_data.ts +53 -0
  24. package/src/components/collection_editor/properties/BlockPropertyField.tsx +131 -0
  25. package/src/components/collection_editor/properties/BooleanPropertyField.tsx +36 -0
  26. package/src/components/collection_editor/properties/CommonPropertyFields.tsx +112 -0
  27. package/src/components/collection_editor/properties/DateTimePropertyField.tsx +86 -0
  28. package/src/components/collection_editor/properties/EnumPropertyField.tsx +116 -0
  29. package/src/components/collection_editor/properties/FieldHelperView.tsx +13 -0
  30. package/src/components/collection_editor/properties/KeyValuePropertyField.tsx +20 -0
  31. package/src/components/collection_editor/properties/MapPropertyField.tsx +154 -0
  32. package/src/components/collection_editor/properties/NumberPropertyField.tsx +38 -0
  33. package/src/components/collection_editor/properties/ReferencePropertyField.tsx +184 -0
  34. package/src/components/collection_editor/properties/RepeatPropertyField.tsx +115 -0
  35. package/src/components/collection_editor/properties/StoragePropertyField.tsx +194 -0
  36. package/src/components/collection_editor/properties/StringPropertyField.tsx +85 -0
  37. package/src/components/collection_editor/properties/advanced/AdvancedPropertyValidation.tsx +36 -0
  38. package/src/components/collection_editor/properties/validation/ArrayPropertyValidation.tsx +50 -0
  39. package/src/components/collection_editor/properties/validation/GeneralPropertyValidation.tsx +49 -0
  40. package/src/components/collection_editor/properties/validation/NumberPropertyValidation.tsx +99 -0
  41. package/src/components/collection_editor/properties/validation/StringPropertyValidation.tsx +131 -0
  42. package/src/components/collection_editor/properties/validation/ValidationPanel.tsx +28 -0
  43. package/src/components/collection_editor/templates/blog_template.ts +115 -0
  44. package/src/components/collection_editor/templates/products_template.ts +89 -0
  45. package/src/components/collection_editor/templates/users_template.ts +34 -0
  46. package/src/components/collection_editor/util.ts +21 -0
  47. package/src/components/collection_editor/utils/supported_fields.tsx +28 -0
  48. package/src/components/collection_editor/utils/update_property_for_widget.ts +258 -0
  49. package/src/components/collection_editor/utils/useTraceUpdate.tsx +23 -0
  50. package/src/index.ts +31 -0
  51. package/src/types/collection_editor_controller.tsx +31 -0
  52. package/src/types/collection_inference.ts +3 -0
  53. package/src/types/config_controller.tsx +30 -0
  54. package/src/types/config_permissions.ts +20 -0
  55. package/src/types/persisted_collection.ts +7 -0
  56. package/src/useCollectionEditorController.tsx +9 -0
  57. package/src/useCollectionEditorPlugin.tsx +103 -0
  58. package/src/useCollectionsConfigController.tsx +9 -0
  59. package/src/utils/arrays.ts +3 -0
  60. package/src/utils/entities.ts +38 -0
  61. package/src/utils/icons.ts +17 -0
  62. package/src/utils/join_collections.ts +144 -0
  63. package/src/utils/synonyms.ts +1952 -0
  64. package/src/vite-env.d.ts +1 -0
@@ -0,0 +1,29 @@
1
+ import { Button, Dialog, DialogActions, DialogContent, Typography, useFireCMSContext } from "@firecms/core";
2
+ import React from "react";
3
+
4
+ export function EntityCustomViewsSelectDialog({ open, onClose }: { open: boolean, onClose: (selectedViewKey?: string) => void }) {
5
+ const { entityViews } = useFireCMSContext();
6
+
7
+ return <Dialog
8
+ maxWidth={"md"}
9
+ open={open}>
10
+ <DialogContent>
11
+ <Typography variant={"h6"}>
12
+ Select view
13
+ </Typography>
14
+ {entityViews?.map((view) => {
15
+ return <Button
16
+ key={view.key}
17
+ onClick={() => onClose(view.key)}
18
+ fullWidth
19
+ variant={"text"}
20
+ >
21
+ {view.name} ({view.key})
22
+ </Button>;
23
+ })}
24
+ </DialogContent>
25
+ <DialogActions>
26
+ <Button onClick={() => onClose()}>Cancel</Button>
27
+ </DialogActions>
28
+ </Dialog>
29
+ }
@@ -0,0 +1,354 @@
1
+ import React, { useEffect } from "react";
2
+
3
+ import { FastField, Formik, getIn, useFormikContext } from "formik";
4
+ import {
5
+ AutoAwesomeIcon,
6
+ Button,
7
+ CircularProgress,
8
+ DebouncedTextField,
9
+ Dialog,
10
+ DialogActions,
11
+ DialogContent,
12
+ EnumValueConfig,
13
+ EnumValues,
14
+ FormikArrayContainer,
15
+ IconButton,
16
+ ListIcon,
17
+ LoadingButton,
18
+ Paper,
19
+ SettingsIcon,
20
+ Typography
21
+ } from "@firecms/core";
22
+ import { FieldHelperView } from "./properties/FieldHelperView";
23
+ import { extractEnumFromValues } from "@firecms/schema_inference";
24
+
25
+ type EnumFormProps = {
26
+ enumValues: EnumValueConfig[];
27
+ onValuesChanged?: (enumValues: EnumValueConfig[]) => void;
28
+ onError?: (error: boolean) => void;
29
+ updateIds: boolean;
30
+ disabled: boolean;
31
+ allowDataInference?: boolean;
32
+ getData?: () => Promise<string[]>;
33
+ };
34
+ export const EnumForm = React.memo(
35
+ function EnumForm({
36
+ enumValues,
37
+ onValuesChanged,
38
+ onError,
39
+ updateIds,
40
+ disabled,
41
+ allowDataInference,
42
+ getData
43
+ }: EnumFormProps) {
44
+
45
+ return (
46
+ <Formik initialValues={{ enumValues }}
47
+ // enableReinitialize={true}
48
+ validateOnMount={true}
49
+ onSubmit={(data: { enumValues: EnumValueConfig[] }, formikHelpers) => {
50
+ }}
51
+ >
52
+ {
53
+ ({
54
+ values,
55
+ errors
56
+ }) => {
57
+ // eslint-disable-next-line react-hooks/rules-of-hooks
58
+ useEffect(() => {
59
+ if (onValuesChanged) {
60
+ onValuesChanged(values.enumValues);
61
+ }
62
+ }, [values.enumValues]);
63
+ // eslint-disable-next-line react-hooks/rules-of-hooks
64
+ useEffect(() => {
65
+ if (onError)
66
+ onError(Boolean(errors?.enumValues ?? false));
67
+ }, [errors]);
68
+
69
+ return <EnumFormFields enumValuesPath={"enumValues"}
70
+ values={values}
71
+ errors={errors}
72
+ shouldUpdateId={updateIds}
73
+ disabled={disabled}
74
+ allowDataInference={allowDataInference}
75
+ getData={getData}/>
76
+ }
77
+ }
78
+ </Formik>
79
+
80
+ );
81
+ },
82
+ function areEqual(prevProps: EnumFormProps, nextProps: EnumFormProps) {
83
+ return prevProps.enumValues.length === nextProps.enumValues.length &&
84
+ prevProps.onValuesChanged === nextProps.onValuesChanged &&
85
+ prevProps.getData === nextProps.getData
86
+ ;
87
+ }
88
+ );
89
+
90
+ type EnumFormFieldsProps = {
91
+ values: { enumValues: EnumValueConfig[] };
92
+ errors: any;
93
+ enumValuesPath: string;
94
+ shouldUpdateId: boolean;
95
+ disabled: boolean;
96
+ getData?: () => Promise<string[]>;
97
+ allowDataInference?: boolean;
98
+ };
99
+
100
+ // const EnumFormFields = React.memo(
101
+ function EnumFormFields({
102
+ values,
103
+ errors,
104
+ disabled,
105
+ enumValuesPath,
106
+ shouldUpdateId,
107
+ allowDataInference,
108
+ getData,
109
+ }: EnumFormFieldsProps) {
110
+
111
+ const {
112
+ setFieldValue
113
+ } = useFormikContext();
114
+
115
+ const [lastInternalIdAdded, setLastInternalIdAdded] = React.useState<number | undefined>();
116
+ const [editDialogIndex, setEditDialogIndex] = React.useState<number | undefined>();
117
+ const [inferring, setInferring] = React.useState(false);
118
+
119
+ const inferredValuesRef = React.useRef(new Set());
120
+ const inferredValues = inferredValuesRef.current;
121
+
122
+ const buildEntry = (index: number, internalId: number) => {
123
+ const justAdded = lastInternalIdAdded === internalId;
124
+ return <EnumEntry index={index}
125
+ disabled={disabled}
126
+ enumValuesPath={enumValuesPath}
127
+ autoFocus={justAdded}
128
+ shouldUpdateId={shouldUpdateId || justAdded}
129
+ onDialogOpen={() => setEditDialogIndex(index)}
130
+ inferredEntry={inferredValues.has(values.enumValues[index]?.id as string)}
131
+ key={`${internalId}`}/>;
132
+ };
133
+
134
+ const inferValues = async () => {
135
+
136
+ setInferring(true);
137
+ getData?.().then((data) => {
138
+ if (!data)
139
+ return;
140
+
141
+ const flatData = data.flat();
142
+
143
+ const fieldData = Array.from(new Set(flatData));
144
+
145
+ const currentEnumValues = values.enumValues;
146
+ const foundEnumValues = extractEnumFromValues(fieldData);
147
+
148
+ // add only new enum values
149
+ const newEnumValues = foundEnumValues.filter((enumValue) => {
150
+ return !currentEnumValues?.some((v: any) => v.id === enumValue.id);
151
+ });
152
+
153
+ newEnumValues.forEach((enumValue) => {
154
+ inferredValues.add(enumValue.id);
155
+ });
156
+ setFieldValue(enumValuesPath, [...newEnumValues, ...currentEnumValues]);
157
+ }).catch(e => {
158
+ console.error(e);
159
+ })
160
+ .finally(() => setInferring(false));
161
+ }
162
+
163
+ return (
164
+ <div className={"col-span-12"}>
165
+ <div className="ml-3.5 flex flex-row items-center">
166
+ <ListIcon/>
167
+ <Typography variant={"subtitle2"}
168
+ className="ml-2 grow">
169
+ Values
170
+ </Typography>
171
+ {allowDataInference &&
172
+ <LoadingButton loading={inferring}
173
+ disabled={disabled || inferring}
174
+ variant={"text"} size={"small"} onClick={inferValues}>
175
+ {inferring ? <CircularProgress size={"small"}/> : <AutoAwesomeIcon/>}
176
+ Infer values from data
177
+ </LoadingButton>}
178
+ </div>
179
+
180
+ <Paper className="p-4 m-1">
181
+
182
+ <FormikArrayContainer
183
+ value={values.enumValues}
184
+ addLabel={"Add enum value"}
185
+ name={enumValuesPath}
186
+ buildEntry={buildEntry}
187
+ disabled={disabled}
188
+ onInternalIdAdded={setLastInternalIdAdded}
189
+ small={true}
190
+ setFieldValue={setFieldValue}
191
+ includeAddButton={true}/>
192
+
193
+ <EnumEntryDialog index={editDialogIndex}
194
+ open={editDialogIndex !== undefined}
195
+ enumValuesPath={enumValuesPath}
196
+ onClose={() => setEditDialogIndex(undefined)}/>
197
+ </Paper>
198
+ </div>
199
+ );
200
+ }
201
+
202
+ type EnumEntryProps = {
203
+ index: number,
204
+ enumValuesPath: string,
205
+ shouldUpdateId: boolean,
206
+ autoFocus: boolean,
207
+ onDialogOpen: () => void;
208
+ disabled: boolean;
209
+ inferredEntry?: boolean;
210
+ };
211
+
212
+ const EnumEntry = React.memo(
213
+ function EnumEntryInternal({
214
+ index,
215
+ shouldUpdateId: updateId,
216
+ enumValuesPath,
217
+ autoFocus,
218
+ onDialogOpen,
219
+ disabled,
220
+ inferredEntry
221
+ }: EnumEntryProps) {
222
+
223
+ const {
224
+ values,
225
+ handleChange,
226
+ errors,
227
+ setFieldValue,
228
+ touched
229
+ } = useFormikContext<EnumValues>();
230
+
231
+ const shouldUpdateIdRef = React.useRef(!getIn(values, `${enumValuesPath}[${index}].id`));
232
+ const shouldUpdateId = updateId || shouldUpdateIdRef.current;
233
+
234
+ const idValue = getIn(values, `${enumValuesPath}[${index}].id`);
235
+ const labelValue = getIn(values, `${enumValuesPath}[${index}].label`);
236
+
237
+ const labelError = getIn(errors, `${enumValuesPath}[${index}].label`);
238
+
239
+ const currentLabelRef = React.useRef(labelValue);
240
+
241
+ React.useEffect(() => {
242
+ if ((currentLabelRef.current === idValue || !idValue) && shouldUpdateId) {
243
+ setFieldValue(`${enumValuesPath}[${index}].id`, labelValue);
244
+ }
245
+ currentLabelRef.current = labelValue;
246
+ }, [labelValue]);
247
+
248
+ return (
249
+ <div className={"flex w-full align-center justify-center"}>
250
+ <FastField name={`${enumValuesPath}[${index}].label`}
251
+ as={DebouncedTextField}
252
+ className={"flex-grow"}
253
+ required
254
+ disabled={disabled}
255
+ size="small"
256
+ validate={validateLabel}
257
+ autoFocus={autoFocus}
258
+ autoComplete="off"
259
+ endAdornment={inferredEntry && <AutoAwesomeIcon size={"small"}/>}
260
+ error={Boolean(labelError)}/>
261
+
262
+ {!disabled &&
263
+ <IconButton
264
+ size="small"
265
+ aria-label="edit"
266
+ className={"m-1"}
267
+ onClick={() => onDialogOpen()}>
268
+ <SettingsIcon size={"small"}/>
269
+ </IconButton>}
270
+
271
+ </div>);
272
+ },
273
+ function areEqual(prevProps: EnumEntryProps, nextProps: EnumEntryProps) {
274
+ return prevProps.index === nextProps.index &&
275
+ prevProps.enumValuesPath === nextProps.enumValuesPath &&
276
+ prevProps.shouldUpdateId === nextProps.shouldUpdateId &&
277
+ prevProps.inferredEntry === nextProps.inferredEntry &&
278
+ prevProps.autoFocus === nextProps.autoFocus;
279
+ }
280
+ );
281
+
282
+ function EnumEntryDialog({
283
+ index,
284
+ open,
285
+ onClose,
286
+ enumValuesPath
287
+ }: {
288
+ index?: number;
289
+ open: boolean;
290
+ enumValuesPath: string;
291
+ onClose: () => void;
292
+ }) {
293
+
294
+ const {
295
+ values,
296
+ handleChange,
297
+ errors,
298
+ setFieldValue,
299
+ touched
300
+ } = useFormikContext<EnumValues>();
301
+
302
+ const idError = index !== undefined ? getIn(errors, `${enumValuesPath}[${index}].id`) : undefined;
303
+ return <Dialog
304
+ maxWidth="md"
305
+ aria-labelledby="enum-edit-dialog"
306
+ open={open}
307
+ onOpenChange={(open) => !open ? onClose() : undefined}
308
+ >
309
+
310
+ <DialogContent>
311
+ {index !== undefined &&
312
+ <div><FastField name={`${enumValuesPath}[${index}]id`}
313
+ as={DebouncedTextField}
314
+ required
315
+ validate={validateId}
316
+ label={"ID"}
317
+ size="small"
318
+ autoComplete="off"
319
+ error={Boolean(idError)}/>
320
+
321
+ <FieldHelperView error={Boolean(idError)}>
322
+ {idError ?? "Value saved in the data source"}
323
+ </FieldHelperView>
324
+ </div>}
325
+ </DialogContent>
326
+
327
+ <DialogActions>
328
+ <Button
329
+ autoFocus
330
+ variant="outlined"
331
+ onClick={onClose}
332
+ color="primary">
333
+ Ok
334
+ </Button>
335
+ </DialogActions>
336
+
337
+ </Dialog>
338
+ }
339
+
340
+ function validateLabel(value: string) {
341
+ let error;
342
+ if (!value) {
343
+ error = "You must specify a label";
344
+ }
345
+ return error;
346
+ }
347
+
348
+ function validateId(value: string) {
349
+ let error;
350
+ if (!value) {
351
+ error = "You must specify an ID";
352
+ }
353
+ return error;
354
+ }