@firecms/collection_editor 3.0.0-alpha.5 → 3.0.0-alpha.51

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 (140) hide show
  1. package/dist/ConfigControllerProvider.d.ts +36 -0
  2. package/dist/index.d.ts +11 -0
  3. package/dist/index.es.js +6995 -0
  4. package/dist/index.es.js.map +1 -0
  5. package/dist/index.umd.js +4 -0
  6. package/dist/index.umd.js.map +1 -0
  7. package/dist/types/collection_editor_controller.d.ts +35 -0
  8. package/dist/types/collection_inference.d.ts +2 -0
  9. package/dist/types/config_controller.d.ts +41 -0
  10. package/dist/types/config_permissions.d.ts +19 -0
  11. package/dist/types/persisted_collection.d.ts +6 -0
  12. package/dist/ui/CollectionViewHeaderAction.d.ts +10 -0
  13. package/dist/ui/EditorCollectionAction.d.ts +2 -0
  14. package/dist/ui/HomePageEditorCollectionAction.d.ts +2 -0
  15. package/dist/ui/MissingReferenceWidget.d.ts +3 -0
  16. package/dist/ui/NewCollectionCard.d.ts +2 -0
  17. package/dist/ui/PropertyAddColumnComponent.d.ts +6 -0
  18. package/dist/ui/RootCollectionSuggestions.d.ts +1 -0
  19. package/dist/ui/collection_editor/CollectionDetailsForm.d.ts +9 -0
  20. package/dist/ui/collection_editor/CollectionEditorDialog.d.ts +38 -0
  21. package/dist/ui/collection_editor/CollectionEditorWelcomeView.d.ts +15 -0
  22. package/dist/ui/collection_editor/CollectionPropertiesEditorForm.d.ts +20 -0
  23. package/dist/ui/collection_editor/CollectionYupValidation.d.ts +14 -0
  24. package/dist/ui/collection_editor/EntityCustomViewsSelectDialog.d.ts +4 -0
  25. package/dist/ui/collection_editor/EnumForm.d.ts +13 -0
  26. package/dist/ui/collection_editor/GetCodeDialog.d.ts +5 -0
  27. package/dist/ui/collection_editor/PropertyEditView.d.ts +40 -0
  28. package/dist/ui/collection_editor/PropertyFieldPreview.d.ts +15 -0
  29. package/dist/ui/collection_editor/PropertySelectItem.d.ts +8 -0
  30. package/dist/ui/collection_editor/PropertyTree.d.ts +32 -0
  31. package/dist/ui/collection_editor/SelectIcons.d.ts +6 -0
  32. package/dist/ui/collection_editor/SubcollectionsEditTab.d.ts +12 -0
  33. package/dist/ui/collection_editor/UnsavedChangesDialog.d.ts +9 -0
  34. package/dist/ui/collection_editor/import/CollectionEditorImportDataPreview.d.ts +7 -0
  35. package/dist/ui/collection_editor/import/CollectionEditorImportMapping.d.ts +7 -0
  36. package/dist/ui/collection_editor/import/clean_import_data.d.ts +7 -0
  37. package/dist/ui/collection_editor/properties/BlockPropertyField.d.ts +8 -0
  38. package/dist/ui/collection_editor/properties/BooleanPropertyField.d.ts +3 -0
  39. package/dist/ui/collection_editor/properties/CommonPropertyFields.d.ts +10 -0
  40. package/dist/ui/collection_editor/properties/DateTimePropertyField.d.ts +3 -0
  41. package/dist/ui/collection_editor/properties/EnumPropertyField.d.ts +8 -0
  42. package/dist/ui/collection_editor/properties/FieldHelperView.d.ts +4 -0
  43. package/dist/ui/collection_editor/properties/KeyValuePropertyField.d.ts +3 -0
  44. package/dist/ui/collection_editor/properties/MapPropertyField.d.ts +8 -0
  45. package/dist/ui/collection_editor/properties/NumberPropertyField.d.ts +3 -0
  46. package/dist/ui/collection_editor/properties/ReferencePropertyField.d.ts +13 -0
  47. package/dist/ui/collection_editor/properties/RepeatPropertyField.d.ts +10 -0
  48. package/dist/ui/collection_editor/properties/StoragePropertyField.d.ts +5 -0
  49. package/dist/ui/collection_editor/properties/StringPropertyField.d.ts +5 -0
  50. package/dist/ui/collection_editor/properties/UrlPropertyField.d.ts +4 -0
  51. package/dist/ui/collection_editor/properties/advanced/AdvancedPropertyValidation.d.ts +3 -0
  52. package/dist/ui/collection_editor/properties/validation/ArrayPropertyValidation.d.ts +5 -0
  53. package/dist/ui/collection_editor/properties/validation/GeneralPropertyValidation.d.ts +4 -0
  54. package/dist/ui/collection_editor/properties/validation/NumberPropertyValidation.d.ts +3 -0
  55. package/dist/ui/collection_editor/properties/validation/StringPropertyValidation.d.ts +11 -0
  56. package/dist/ui/collection_editor/properties/validation/ValidationPanel.d.ts +2 -0
  57. package/dist/ui/collection_editor/templates/blog_template.d.ts +2 -0
  58. package/dist/ui/collection_editor/templates/products_template.d.ts +2 -0
  59. package/dist/ui/collection_editor/templates/users_template.d.ts +2 -0
  60. package/dist/ui/collection_editor/util.d.ts +4 -0
  61. package/dist/ui/collection_editor/utils/strings.d.ts +1 -0
  62. package/dist/ui/collection_editor/utils/supported_fields.d.ts +3 -0
  63. package/dist/ui/collection_editor/utils/update_property_for_widget.d.ts +2 -0
  64. package/dist/ui/collection_editor/utils/useTraceUpdate.d.ts +1 -0
  65. package/dist/useCollectionEditorController.d.ts +6 -0
  66. package/dist/useCollectionEditorPlugin.d.ts +45 -0
  67. package/dist/useCollectionsConfigController.d.ts +6 -0
  68. package/dist/utils/arrays.d.ts +1 -0
  69. package/dist/utils/entities.d.ts +3 -0
  70. package/dist/utils/icons.d.ts +1 -0
  71. package/dist/utils/synonyms.d.ts +1951 -0
  72. package/package.json +26 -23
  73. package/src/ConfigControllerProvider.tsx +321 -0
  74. package/src/index.ts +35 -0
  75. package/src/types/collection_editor_controller.tsx +42 -0
  76. package/src/types/collection_inference.ts +3 -0
  77. package/src/types/config_controller.tsx +50 -0
  78. package/src/types/config_permissions.ts +20 -0
  79. package/src/types/persisted_collection.ts +9 -0
  80. package/src/ui/CollectionViewHeaderAction.tsx +42 -0
  81. package/src/ui/EditorCollectionAction.tsx +95 -0
  82. package/src/ui/HomePageEditorCollectionAction.tsx +88 -0
  83. package/src/ui/MissingReferenceWidget.tsx +34 -0
  84. package/src/ui/NewCollectionCard.tsx +46 -0
  85. package/src/ui/PropertyAddColumnComponent.tsx +41 -0
  86. package/src/ui/RootCollectionSuggestions.tsx +62 -0
  87. package/src/ui/collection_editor/CollectionDetailsForm.tsx +353 -0
  88. package/src/ui/collection_editor/CollectionEditorDialog.tsx +744 -0
  89. package/src/ui/collection_editor/CollectionEditorWelcomeView.tsx +212 -0
  90. package/src/ui/collection_editor/CollectionPropertiesEditorForm.tsx +480 -0
  91. package/src/ui/collection_editor/CollectionYupValidation.tsx +7 -0
  92. package/src/ui/collection_editor/EntityCustomViewsSelectDialog.tsx +36 -0
  93. package/src/ui/collection_editor/EnumForm.tsx +356 -0
  94. package/src/ui/collection_editor/GetCodeDialog.tsx +118 -0
  95. package/src/ui/collection_editor/PropertyEditView.tsx +565 -0
  96. package/src/ui/collection_editor/PropertyFieldPreview.tsx +201 -0
  97. package/src/ui/collection_editor/PropertySelectItem.tsx +31 -0
  98. package/src/ui/collection_editor/PropertyTree.tsx +238 -0
  99. package/src/ui/collection_editor/SelectIcons.tsx +72 -0
  100. package/src/ui/collection_editor/SubcollectionsEditTab.tsx +252 -0
  101. package/src/ui/collection_editor/UnsavedChangesDialog.tsx +47 -0
  102. package/src/ui/collection_editor/import/CollectionEditorImportDataPreview.tsx +37 -0
  103. package/src/ui/collection_editor/import/CollectionEditorImportMapping.tsx +275 -0
  104. package/src/ui/collection_editor/import/clean_import_data.ts +53 -0
  105. package/src/ui/collection_editor/properties/BlockPropertyField.tsx +134 -0
  106. package/src/ui/collection_editor/properties/BooleanPropertyField.tsx +36 -0
  107. package/src/ui/collection_editor/properties/CommonPropertyFields.tsx +111 -0
  108. package/src/ui/collection_editor/properties/DateTimePropertyField.tsx +86 -0
  109. package/src/ui/collection_editor/properties/EnumPropertyField.tsx +116 -0
  110. package/src/ui/collection_editor/properties/FieldHelperView.tsx +13 -0
  111. package/src/ui/collection_editor/properties/KeyValuePropertyField.tsx +20 -0
  112. package/src/ui/collection_editor/properties/MapPropertyField.tsx +157 -0
  113. package/src/ui/collection_editor/properties/NumberPropertyField.tsx +38 -0
  114. package/src/ui/collection_editor/properties/ReferencePropertyField.tsx +184 -0
  115. package/src/ui/collection_editor/properties/RepeatPropertyField.tsx +107 -0
  116. package/src/ui/collection_editor/properties/StoragePropertyField.tsx +194 -0
  117. package/src/ui/collection_editor/properties/StringPropertyField.tsx +79 -0
  118. package/src/ui/collection_editor/properties/UrlPropertyField.tsx +89 -0
  119. package/src/ui/collection_editor/properties/advanced/AdvancedPropertyValidation.tsx +36 -0
  120. package/src/ui/collection_editor/properties/validation/ArrayPropertyValidation.tsx +50 -0
  121. package/src/ui/collection_editor/properties/validation/GeneralPropertyValidation.tsx +49 -0
  122. package/src/ui/collection_editor/properties/validation/NumberPropertyValidation.tsx +99 -0
  123. package/src/ui/collection_editor/properties/validation/StringPropertyValidation.tsx +131 -0
  124. package/src/ui/collection_editor/properties/validation/ValidationPanel.tsx +28 -0
  125. package/src/ui/collection_editor/templates/blog_template.ts +115 -0
  126. package/src/ui/collection_editor/templates/products_template.ts +88 -0
  127. package/src/ui/collection_editor/templates/users_template.ts +34 -0
  128. package/src/ui/collection_editor/util.ts +21 -0
  129. package/src/ui/collection_editor/utils/strings.ts +8 -0
  130. package/src/ui/collection_editor/utils/supported_fields.tsx +29 -0
  131. package/src/ui/collection_editor/utils/update_property_for_widget.ts +271 -0
  132. package/src/ui/collection_editor/utils/useTraceUpdate.tsx +23 -0
  133. package/src/useCollectionEditorController.tsx +9 -0
  134. package/src/useCollectionEditorPlugin.tsx +128 -0
  135. package/src/useCollectionsConfigController.tsx +9 -0
  136. package/src/utils/arrays.ts +3 -0
  137. package/src/utils/entities.ts +38 -0
  138. package/src/utils/icons.ts +17 -0
  139. package/src/utils/synonyms.ts +1952 -0
  140. package/src/vite-env.d.ts +1 -0
@@ -0,0 +1,212 @@
1
+ import React, { useEffect, useState } from "react";
2
+ import {
3
+ Button,
4
+ Card,
5
+ Chip,
6
+ CircularProgress,
7
+ cn,
8
+ Container,
9
+ EntityCollection,
10
+ Icon,
11
+ Tooltip,
12
+ Typography,
13
+ unslugify
14
+ } from "@firecms/core";
15
+ import { useFormikContext } from "formik";
16
+
17
+ import { productsCollectionTemplate } from "./templates/products_template";
18
+ import { blogCollectionTemplate } from "./templates/blog_template";
19
+ import { usersCollectionTemplate } from "./templates/users_template";
20
+ import { ImportFileUpload } from "@firecms/data_import_export";
21
+
22
+ export function CollectionEditorWelcomeView({
23
+ path,
24
+ pathSuggestions,
25
+ parentCollection,
26
+ onContinue,
27
+ collections
28
+ }: {
29
+ path: string;
30
+ pathSuggestions?: (path: string) => Promise<string[]>;
31
+ parentCollection?: EntityCollection;
32
+ onContinue: (importData?: object[]) => void;
33
+ collections?: EntityCollection[];
34
+ }) {
35
+
36
+ const [loadingPathSuggestions, setLoadingPathSuggestions] = useState(false);
37
+ const [filteredPathSuggestions, setFilteredPathSuggestions] = useState<string[] | undefined>();
38
+ useEffect(() => {
39
+ if (pathSuggestions && collections) {
40
+ setLoadingPathSuggestions(true);
41
+ pathSuggestions(path)
42
+ .then(suggestions => {
43
+ const filteredSuggestions = suggestions.filter(s => !collections.find(c => c.path.trim().toLowerCase() === s.trim().toLowerCase()));
44
+ setFilteredPathSuggestions(filteredSuggestions);
45
+ })
46
+ .finally(() => setLoadingPathSuggestions(false));
47
+ }
48
+ }, [collections, path, pathSuggestions]);
49
+
50
+ const {
51
+ values,
52
+ setFieldValue,
53
+ setValues,
54
+ handleChange,
55
+ touched,
56
+ errors,
57
+ setFieldTouched,
58
+ isSubmitting,
59
+ submitCount
60
+ } = useFormikContext<EntityCollection>();
61
+
62
+ return (
63
+ <div className={"overflow-auto my-auto"}>
64
+ <Container maxWidth={"4xl"} className={"flex flex-col gap-4 p-8 m-auto"}>
65
+
66
+ <div
67
+ className="flex flex-row py-2 pt-3 items-center">
68
+ <Typography variant={"h4"} className={"flex-grow"}>
69
+ New collection
70
+ </Typography>
71
+ </div>
72
+
73
+ {parentCollection && <Chip colorScheme={"tealDarker"}>
74
+ <Typography variant={"caption"}>
75
+ This is a subcollection of <b>{parentCollection.name}</b>
76
+ </Typography>
77
+ </Chip>}
78
+
79
+ <div className={"my-2"}>
80
+ <Typography variant={"caption"}
81
+ color={"secondary"}>
82
+ ● Use one of the existing paths in your database:
83
+ </Typography>
84
+ <div className={"flex flex-wrap gap-x-2 gap-y-1 items-center my-2 min-h-7"}>
85
+
86
+ {loadingPathSuggestions && !filteredPathSuggestions && <CircularProgress size={"small"}/>}
87
+
88
+ {filteredPathSuggestions?.map((suggestion, index) => (
89
+ <Chip key={suggestion}
90
+ colorScheme={"cyanLighter"}
91
+ onClick={() => {
92
+ setFieldValue("name", unslugify(suggestion));
93
+ setFieldValue("path", suggestion);
94
+ setFieldValue("properties", undefined);
95
+ onContinue();
96
+ }}
97
+ size="small">
98
+ {suggestion}
99
+ </Chip>
100
+ ))}
101
+
102
+ {!loadingPathSuggestions && filteredPathSuggestions?.length === 0 &&
103
+ <Typography variant={"caption"}>
104
+ No suggestions
105
+ </Typography>
106
+ }
107
+
108
+ </div>
109
+
110
+ </div>
111
+
112
+ <div className={"my-2"}>
113
+ <Typography variant={"caption"}
114
+ color={"secondary"}>
115
+ ● Select a template:
116
+ </Typography>
117
+
118
+ <div className={"flex gap-4"}>
119
+ <TemplateButton title={"Products"}
120
+ subtitle={"A collection of products with images, prices and stock"}
121
+ icon={<Icon size={"small"} iconKey={productsCollectionTemplate.icon!}/>}
122
+ onClick={() => {
123
+ setValues(productsCollectionTemplate);
124
+ onContinue();
125
+ }}/>
126
+ <TemplateButton title={"Blog posts"}
127
+ subtitle={"A collection of blog posts with images, authors and complex content"}
128
+ icon={<Icon size={"small"} iconKey={blogCollectionTemplate.icon!}/>}
129
+ onClick={() => {
130
+ setValues(blogCollectionTemplate);
131
+ onContinue();
132
+ }}/>
133
+ <TemplateButton title={"Users"}
134
+ subtitle={"A collection of users with emails, names and roles"}
135
+ icon={<Icon size={"small"} iconKey={usersCollectionTemplate.icon!}/>}
136
+ onClick={() => {
137
+ setValues(usersCollectionTemplate);
138
+ onContinue();
139
+ }}/>
140
+ </div>
141
+
142
+ </div>
143
+
144
+ {!parentCollection && <div>
145
+
146
+ <Typography variant={"caption"}
147
+ color={"secondary"}
148
+ className={"mb-2"}>
149
+ ● Create a collection from a file (csv, json, xls, xslx...)
150
+ </Typography>
151
+
152
+ <ImportFileUpload onDataAdded={(data) => onContinue(data)}/>
153
+
154
+ </div>}
155
+
156
+ <div>
157
+
158
+ <Button variant={"text"} onClick={() => onContinue()} className={"my-2"}>
159
+ Continue from scratch
160
+ </Button>
161
+ </div>
162
+
163
+ {/*<div style={{ height: "52px" }}/>*/}
164
+
165
+ </Container>
166
+ </div>
167
+ );
168
+ }
169
+
170
+ export function TemplateButton({
171
+ title,
172
+ subtitle,
173
+ icon,
174
+ onClick
175
+ }: {
176
+ title: string,
177
+ icon: React.ReactNode,
178
+ subtitle: string,
179
+ onClick?: () => void
180
+ }) {
181
+
182
+ return (
183
+ <Tooltip title={subtitle}>
184
+ <Card
185
+ onClick={onClick}
186
+ className={cn(
187
+ "my-2 rounded-md border mx-0 p-6 px-4 focus:outline-none transition ease-in-out duration-150 flex flex-row gap-4 items-center",
188
+ "text-gray-700 dark:text-gray-300",
189
+ "hover:border-blue-600 hover:text-blue-600 dark:hover:text-blue-400 focus:ring-blue-400 hover:ring-1 hover:ring-primary",
190
+ // "border-transparent hover:bg-primary hover:bg-opacity-10",
191
+ // "my-2 cursor-pointer max-w-sm p-6 border border-solid rounded-lg flex flex-row gap-4 items-center bg-gray-50 dark:bg-gray-800 ",
192
+ "border-gray-400 dark:border-gray-600 "
193
+ )}
194
+ >
195
+ {icon}
196
+ <div
197
+ className={"flex flex-col items-start"}
198
+ >
199
+
200
+ <Typography variant={"subtitle1"}>
201
+ {title}
202
+ </Typography>
203
+ {/*<Typography>*/}
204
+ {/* {subtitle}*/}
205
+ {/*</Typography>*/}
206
+
207
+ </div>
208
+ </Card>
209
+ </Tooltip>
210
+ );
211
+
212
+ }
@@ -0,0 +1,480 @@
1
+ import React, { useEffect, useMemo, useState } from "react";
2
+
3
+ import { Field, FormikErrors, getIn, useFormikContext } from "formik";
4
+ import {
5
+ AddIcon,
6
+ AutoAwesomeIcon,
7
+ Button,
8
+ CircularProgress,
9
+ cn,
10
+ CodeIcon,
11
+ DebouncedTextField,
12
+ defaultBorderMixin,
13
+ EntityCollection,
14
+ ErrorBoundary,
15
+ IconButton,
16
+ isPropertyBuilder,
17
+ makePropertiesEditable,
18
+ Paper,
19
+ Properties,
20
+ Property,
21
+ PropertyConfig,
22
+ PropertyOrBuilder,
23
+ Tooltip,
24
+ Typography,
25
+ useLargeLayout,
26
+ User,
27
+ useSnackbarController
28
+ } from "@firecms/core";
29
+
30
+ import { getFullId, idToPropertiesPath, namespaceToPropertiesOrderPath } from "./util";
31
+ import { OnPropertyChangedParams, PropertyForm, PropertyFormDialog } from "./PropertyEditView";
32
+ import { PropertyTree } from "./PropertyTree";
33
+ import { PersistedCollection } from "../../types/persisted_collection";
34
+ import { GetCodeDialog } from "./GetCodeDialog";
35
+
36
+ type CollectionEditorFormProps = {
37
+ showErrors: boolean;
38
+ isNewCollection: boolean;
39
+ propertyErrorsRef?: React.MutableRefObject<any>;
40
+ onPropertyError: (propertyKey: string, namespace: string | undefined, error?: FormikErrors<any>) => void;
41
+ setDirty?: (dirty: boolean) => void;
42
+ reservedGroups?: string[];
43
+ extraIcon: React.ReactNode;
44
+ getUser: (uid: string) => User | null;
45
+ getData?: () => Promise<object[]>;
46
+ doCollectionInference: (collection: PersistedCollection) => Promise<Partial<EntityCollection> | null> | undefined;
47
+ propertyConfigs: Record<string, PropertyConfig>;
48
+ collectionEditable: boolean;
49
+ };
50
+
51
+ export function CollectionPropertiesEditorForm({
52
+ showErrors,
53
+ isNewCollection,
54
+ propertyErrorsRef,
55
+ onPropertyError,
56
+ setDirty,
57
+ reservedGroups,
58
+ extraIcon,
59
+ getUser,
60
+ getData,
61
+ doCollectionInference,
62
+ propertyConfigs,
63
+ collectionEditable
64
+ }: CollectionEditorFormProps) {
65
+
66
+ const {
67
+ values,
68
+ setFieldValue,
69
+ setFieldError,
70
+ setFieldTouched,
71
+ errors,
72
+ dirty
73
+ } = useFormikContext<PersistedCollection>();
74
+
75
+ const snackbarController = useSnackbarController();
76
+
77
+ const largeLayout = useLargeLayout("lg");
78
+ const asDialog = !largeLayout
79
+
80
+ // index of the selected property within the namespace
81
+ const [selectedPropertyIndex, setSelectedPropertyIndex] = useState<number | undefined>();
82
+ const [selectedPropertyKey, setSelectedPropertyKey] = useState<string | undefined>();
83
+ const [selectedPropertyNamespace, setSelectedPropertyNamespace] = useState<string | undefined>();
84
+
85
+ const selectedPropertyFullId = selectedPropertyKey ? getFullId(selectedPropertyKey, selectedPropertyNamespace) : undefined;
86
+ const selectedProperty = selectedPropertyFullId ? getIn(values.properties, selectedPropertyFullId.replaceAll(".", ".properties.")) : undefined;
87
+ const [codeDialogOpen, setCodeDialogOpen] = useState<boolean>(false);
88
+
89
+ const [inferringProperties, setInferringProperties] = useState<boolean>(false);
90
+
91
+ const [newPropertyDialogOpen, setNewPropertyDialogOpen] = useState<boolean>(false);
92
+ const [inferredPropertyKeys, setInferredPropertyKeys] = useState<string[]>([]);
93
+
94
+ const currentPropertiesOrderRef = React.useRef<{
95
+ [key: string]: string[]
96
+ }>(values.propertiesOrder ? { "": values.propertiesOrder } : {});
97
+
98
+ useEffect(() => {
99
+ if (setDirty)
100
+ setDirty(dirty);
101
+ }, [dirty]);
102
+
103
+ const inferPropertiesFromData = doCollectionInference
104
+ ? (): void => {
105
+ if (!doCollectionInference)
106
+ return;
107
+
108
+ setInferringProperties(true);
109
+ // @ts-ignore
110
+ doCollectionInference(values)
111
+ .then((newCollection) => {
112
+
113
+ if (newCollection)
114
+ makePropertiesEditable(newCollection.properties as Properties);
115
+
116
+ if (!newCollection) {
117
+ snackbarController.open({
118
+ type: "error",
119
+ message: "Could not infer properties from data"
120
+ });
121
+ return;
122
+ }
123
+ // find properties in the new collection, not present in the current one
124
+ const newPropertyKeys = (newCollection.properties ? Object.keys(newCollection.properties) : [])
125
+ .filter((propertyKey) => !values.properties[propertyKey]);
126
+ if (newPropertyKeys.length === 0) {
127
+ snackbarController.open({
128
+ type: "info",
129
+ message: "No new properties found in existing data"
130
+ });
131
+ return;
132
+ }
133
+ // add them to the current collection
134
+ const updatedProperties = {
135
+ ...newPropertyKeys.reduce((acc, propertyKey) => {
136
+ acc[propertyKey] = (newCollection.properties ?? {})[propertyKey];
137
+ return acc;
138
+ }, {} as { [key: string]: PropertyOrBuilder }),
139
+ ...values.properties
140
+ };
141
+ const updatedPropertiesOrder = [
142
+ ...newPropertyKeys,
143
+ ...(values.propertiesOrder ?? [])
144
+ ];
145
+ setFieldValue("properties", updatedProperties, false);
146
+
147
+ updatePropertiesOrder(updatedPropertiesOrder);
148
+
149
+ setInferredPropertyKeys(newPropertyKeys);
150
+ })
151
+ .finally(() => {
152
+ setInferringProperties(false);
153
+ })
154
+ }
155
+ : undefined;
156
+
157
+ const getCurrentPropertiesOrder = (namespace?: string) => {
158
+ if (!namespace) return currentPropertiesOrderRef.current[""];
159
+ return currentPropertiesOrderRef.current[namespace] ?? getIn(values, namespaceToPropertiesOrderPath(namespace));
160
+ }
161
+
162
+ const updatePropertiesOrder = (newPropertiesOrder: string[], namespace?: string) => {
163
+ const propertiesOrderPath = namespaceToPropertiesOrderPath(namespace);
164
+
165
+ setFieldValue(propertiesOrderPath, newPropertiesOrder, false);
166
+ currentPropertiesOrderRef.current[namespace ?? ""] = newPropertiesOrder;
167
+
168
+ };
169
+
170
+ const deleteProperty = (propertyKey?: string, namespace?: string) => {
171
+ const fullId = propertyKey ? getFullId(propertyKey, namespace) : undefined;
172
+ if (!fullId)
173
+ throw Error("collection editor miss config");
174
+
175
+ setFieldValue(idToPropertiesPath(fullId), undefined, false);
176
+
177
+ const currentPropertiesOrder = getCurrentPropertiesOrder(namespace);
178
+ const newPropertiesOrder = currentPropertiesOrder.filter((p) => p !== propertyKey);
179
+ updatePropertiesOrder(newPropertiesOrder, namespace);
180
+
181
+ setNewPropertyDialogOpen(false);
182
+
183
+ setSelectedPropertyIndex(undefined);
184
+ setSelectedPropertyKey(undefined);
185
+ setSelectedPropertyNamespace(undefined);
186
+ };
187
+
188
+ const onPropertyMove = (propertiesOrder: string[], namespace?: string) => {
189
+ setFieldValue(namespaceToPropertiesOrderPath(namespace), propertiesOrder, false);
190
+ };
191
+
192
+ const onPropertyCreated = ({
193
+ id,
194
+ property
195
+ }: {
196
+ id?: string,
197
+ property: Property
198
+ }) => {
199
+ console.log("onPropertyCreated", {
200
+ id,
201
+ property
202
+ })
203
+ if (!id) {
204
+ throw Error("Need to include an ID when creating a new property")
205
+ }
206
+ setFieldValue("properties", {
207
+ ...(values.properties ?? {}),
208
+ [id]: property
209
+ }, false);
210
+ const newPropertiesOrder = [...(values.propertiesOrder ?? Object.keys(values.properties)), id];
211
+
212
+ console.log("onPropertyCreated", {
213
+ id,
214
+ property,
215
+ newPropertiesOrder
216
+ })
217
+ updatePropertiesOrder(newPropertiesOrder);
218
+
219
+ setNewPropertyDialogOpen(false);
220
+ if (largeLayout) {
221
+ setSelectedPropertyIndex(newPropertiesOrder.indexOf(id));
222
+ setSelectedPropertyKey(id);
223
+ }
224
+ setSelectedPropertyNamespace(undefined);
225
+ };
226
+
227
+ const onPropertyChanged = ({
228
+ id,
229
+ property,
230
+ previousId,
231
+ namespace
232
+ }: OnPropertyChangedParams) => {
233
+ const fullId = id ? getFullId(id, namespace) : undefined;
234
+ const propertyPath = fullId ? idToPropertiesPath(fullId) : undefined;
235
+
236
+ // If the id has changed we need to a little cleanup
237
+ if (previousId && previousId !== id) {
238
+ const previousFullId = getFullId(previousId, namespace);
239
+ const previousPropertyPath = idToPropertiesPath(previousFullId);
240
+
241
+ const currentPropertiesOrder = getCurrentPropertiesOrder(namespace);
242
+
243
+ // replace previousId with id in propertiesOrder
244
+ const newPropertiesOrder = currentPropertiesOrder
245
+ .map((p) => p === previousId ? id : p)
246
+ .filter((p) => p !== undefined) as string[];
247
+
248
+ updatePropertiesOrder(newPropertiesOrder, namespace);
249
+
250
+ if (id) {
251
+ setSelectedPropertyIndex(newPropertiesOrder.indexOf(id));
252
+ setSelectedPropertyKey(id);
253
+ }
254
+ setFieldValue(previousPropertyPath, undefined, false);
255
+ setFieldTouched(previousPropertyPath, false, false);
256
+ }
257
+
258
+ if (propertyPath) {
259
+ setFieldValue(propertyPath, property, false);
260
+ setFieldTouched(propertyPath, true, false);
261
+ }
262
+
263
+ };
264
+
265
+ const onPropertyErrorInternal = (id: string, namespace?: string, error?: FormikErrors<any>) => {
266
+ const propertyPath = id ? getFullId(id, namespace) : undefined;
267
+ console.warn("onPropertyErrorInternal", {
268
+ id,
269
+ namespace,
270
+ error,
271
+ propertyPath
272
+ });
273
+ if (propertyPath) {
274
+ const hasError = error && Object.keys(error).length > 0;
275
+ onPropertyError(id, namespace, hasError ? error : undefined);
276
+ setFieldError(idToPropertiesPath(propertyPath), hasError ? "Property error" : undefined);
277
+ }
278
+ };
279
+
280
+ const closePropertyDialog = () => {
281
+ setSelectedPropertyIndex(undefined);
282
+ setSelectedPropertyKey(undefined);
283
+ };
284
+
285
+ const initialErrors = selectedPropertyKey && propertyErrorsRef?.current?.properties ? propertyErrorsRef.current.properties[selectedPropertyKey] : undefined;
286
+
287
+ const emptyCollection = values?.propertiesOrder === undefined || values.propertiesOrder.length === 0;
288
+
289
+ const usedPropertiesOrder = (values.propertiesOrder
290
+ ? values.propertiesOrder
291
+ : Object.keys(values.properties)) as string[];
292
+
293
+ const owner = useMemo(() => getUser(values.ownerId), [getUser, values.ownerId]);
294
+ const body = (
295
+ <div className={"grid grid-cols-12 gap-2 h-full bg-gray-50 dark:bg-gray-900"}>
296
+ <div className={cn(
297
+ "p-4 md:p-8 pb-20 md:pb-20",
298
+ "col-span-12 lg:col-span-5 h-full overflow-auto",
299
+ !asDialog && "border-r " + defaultBorderMixin
300
+ )}>
301
+
302
+ <div className="flex my-2">
303
+
304
+ <div className="flex-grow mb-4">
305
+
306
+ <Field
307
+ name={"name"}
308
+ as={DebouncedTextField}
309
+ invisible={true}
310
+ className="-ml-1"
311
+ inputClassName="text-2xl font-headers"
312
+ placeholder={"Collection name"}
313
+ size={"small"}
314
+ required
315
+ error={Boolean(errors?.name)}/>
316
+
317
+ {owner &&
318
+ <Typography variant={"body2"}
319
+ className={"ml-2"}
320
+ color={"secondary"}>
321
+ Created by {owner.displayName}
322
+ </Typography>}
323
+ </div>
324
+
325
+ {extraIcon && <div className="ml-4">
326
+ {extraIcon}
327
+ </div>}
328
+
329
+ <div className="ml-1 mt-2 flex flex-row gap-2">
330
+ <Tooltip title={"Get the code for this collection"}>
331
+ <IconButton
332
+ variant={"filled"}
333
+ disabled={inferringProperties}
334
+ onClick={() => setCodeDialogOpen(true)}>
335
+ <CodeIcon/>
336
+ </IconButton>
337
+ </Tooltip>
338
+ {inferPropertiesFromData && <Tooltip title={"Add new properties based on data"}>
339
+ <IconButton
340
+ variant={"filled"}
341
+ disabled={inferringProperties}
342
+ onClick={inferPropertiesFromData}>
343
+ {inferringProperties ? <CircularProgress size={"small"}/> : <AutoAwesomeIcon/>}
344
+ </IconButton>
345
+ </Tooltip>}
346
+ <Tooltip title={"Add new property"}>
347
+ <Button
348
+ variant={"outlined"}
349
+ onClick={() => setNewPropertyDialogOpen(true)}>
350
+ <AddIcon/>
351
+ </Button>
352
+ </Tooltip>
353
+ </div>
354
+ </div>
355
+
356
+ <ErrorBoundary>
357
+ <PropertyTree
358
+ className={"pl-8"}
359
+ onPropertyClick={(propertyKey, namespace) => {
360
+ setSelectedPropertyIndex(usedPropertiesOrder.indexOf(propertyKey));
361
+ setSelectedPropertyKey(propertyKey);
362
+ setSelectedPropertyNamespace(namespace);
363
+ }}
364
+ inferredPropertyKeys={inferredPropertyKeys}
365
+ selectedPropertyKey={selectedPropertyKey ? getFullId(selectedPropertyKey, selectedPropertyNamespace) : undefined}
366
+ properties={values.properties}
367
+ additionalFields={values.additionalFields}
368
+ propertiesOrder={usedPropertiesOrder}
369
+ onPropertyMove={onPropertyMove}
370
+ onPropertyRemove={isNewCollection ? deleteProperty : undefined}
371
+ collectionEditable={collectionEditable}
372
+ errors={showErrors ? errors : {}}/>
373
+ </ErrorBoundary>
374
+
375
+ <Button className={"mt-8 w-full"}
376
+ color="primary"
377
+ variant={"outlined"}
378
+ size={"large"}
379
+ onClick={() => setNewPropertyDialogOpen(true)}
380
+ startIcon={<AddIcon/>}>
381
+ Add new property
382
+ </Button>
383
+ </div>
384
+
385
+ {!asDialog &&
386
+ <div className={"col-span-12 lg:col-span-7 ml-2 p-4 md:p-8 h-full overflow-auto pb-20 md:pb-20"}>
387
+ <Paper
388
+ className="sticky top-8 p-4 min-h-full border border-transparent w-full flex flex-col justify-center ">
389
+
390
+ {selectedPropertyFullId &&
391
+ selectedProperty &&
392
+ !isPropertyBuilder(selectedProperty) &&
393
+ <PropertyForm
394
+ inArray={false}
395
+ key={`edit_view_${selectedPropertyIndex}`}
396
+ existingProperty={!isNewCollection}
397
+ autoUpdateId={false}
398
+ allowDataInference={!isNewCollection}
399
+ autoOpenTypeSelect={false}
400
+ propertyKey={selectedPropertyKey}
401
+ propertyNamespace={selectedPropertyNamespace}
402
+ property={selectedProperty}
403
+ onPropertyChanged={onPropertyChanged}
404
+ onDelete={deleteProperty}
405
+ onError={onPropertyErrorInternal}
406
+ forceShowErrors={showErrors}
407
+ initialErrors={initialErrors}
408
+ getData={getData}
409
+ propertyConfigs={propertyConfigs}
410
+ collectionEditable={collectionEditable}
411
+ />}
412
+
413
+ {!selectedProperty &&
414
+ <Typography variant={"label"} className="flex items-center justify-center h-full">
415
+ {emptyCollection
416
+ ? "Now you can add your first property"
417
+ : "Select a property to edit it"}
418
+ </Typography>}
419
+
420
+ {selectedProperty && isPropertyBuilder(selectedProperty) &&
421
+ <Typography variant={"label"} className="flex items-center justify-center">
422
+ {"This property is defined as a property builder in code"}
423
+ </Typography>}
424
+ </Paper>
425
+ </div>}
426
+
427
+ {asDialog && <PropertyFormDialog
428
+ inArray={false}
429
+ open={selectedPropertyIndex !== undefined}
430
+ key={`edit_view_${selectedPropertyIndex}`}
431
+ autoUpdateId={isNewCollection}
432
+ allowDataInference={!isNewCollection}
433
+ existingProperty={true}
434
+ autoOpenTypeSelect={false}
435
+ propertyKey={selectedPropertyKey}
436
+ propertyNamespace={selectedPropertyNamespace}
437
+ property={selectedProperty}
438
+ onPropertyChanged={onPropertyChanged}
439
+ onDelete={deleteProperty}
440
+ onError={onPropertyErrorInternal}
441
+ forceShowErrors={showErrors}
442
+ initialErrors={initialErrors}
443
+ getData={getData}
444
+ propertyConfigs={propertyConfigs}
445
+ collectionEditable={collectionEditable}
446
+ onOkClicked={asDialog
447
+ ? closePropertyDialog
448
+ : undefined
449
+ }/>}
450
+
451
+ </div>);
452
+
453
+ return (<>
454
+
455
+ {body}
456
+
457
+ {/* This is the dialog used for new properties*/}
458
+ <PropertyFormDialog
459
+ inArray={false}
460
+ existingProperty={false}
461
+ autoOpenTypeSelect={true}
462
+ autoUpdateId={true}
463
+ forceShowErrors={showErrors}
464
+ open={newPropertyDialogOpen}
465
+ onCancel={() => setNewPropertyDialogOpen(false)}
466
+ onPropertyChanged={onPropertyCreated}
467
+ getData={getData}
468
+ allowDataInference={!isNewCollection}
469
+ propertyConfigs={propertyConfigs}
470
+ collectionEditable={collectionEditable}
471
+ existingPropertyKeys={values.propertiesOrder as string[]}/>
472
+
473
+ <GetCodeDialog
474
+ collection={values}
475
+ open={codeDialogOpen}
476
+ onOpenChange={setCodeDialogOpen}/>
477
+
478
+ </>
479
+ );
480
+ }