@firecms/collection_editor 3.0.0-alpha.9 → 3.0.0-beta.2-pre.1

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 (145) hide show
  1. package/dist/ConfigControllerProvider.d.ts +3 -2
  2. package/dist/index.d.ts +3 -2
  3. package/dist/index.es.js +3128 -4094
  4. package/dist/index.es.js.map +1 -1
  5. package/dist/index.umd.js +3 -1
  6. package/dist/index.umd.js.map +1 -1
  7. package/dist/types/collection_editor_controller.d.ts +22 -7
  8. package/dist/types/collection_inference.d.ts +1 -1
  9. package/dist/types/config_controller.d.ts +32 -5
  10. package/dist/types/persisted_collection.d.ts +3 -1
  11. package/dist/ui/CollectionViewHeaderAction.d.ts +10 -0
  12. package/dist/{components → ui}/EditorCollectionAction.d.ts +1 -1
  13. package/dist/ui/MissingReferenceWidget.d.ts +3 -0
  14. package/dist/ui/NewCollectionButton.d.ts +1 -0
  15. package/dist/ui/PropertyAddColumnComponent.d.ts +6 -0
  16. package/dist/ui/RootCollectionSuggestions.d.ts +1 -0
  17. package/dist/{components → ui}/collection_editor/CollectionDetailsForm.d.ts +3 -2
  18. package/dist/{components → ui}/collection_editor/CollectionEditorDialog.d.ts +11 -6
  19. package/dist/{components → ui}/collection_editor/CollectionPropertiesEditorForm.d.ts +6 -3
  20. package/dist/{components → ui}/collection_editor/CollectionYupValidation.d.ts +3 -0
  21. package/dist/ui/collection_editor/EntityCustomViewsSelectDialog.d.ts +4 -0
  22. package/dist/ui/collection_editor/GetCodeDialog.d.ts +5 -0
  23. package/dist/{components → ui}/collection_editor/PropertyEditView.d.ts +8 -6
  24. package/dist/{components → ui}/collection_editor/PropertyFieldPreview.d.ts +4 -3
  25. package/dist/ui/collection_editor/PropertySelectItem.d.ts +8 -0
  26. package/dist/{components → ui}/collection_editor/PropertyTree.d.ts +8 -5
  27. package/dist/{components → ui}/collection_editor/SubcollectionsEditTab.d.ts +2 -2
  28. package/dist/{components → ui}/collection_editor/import/CollectionEditorImportDataPreview.d.ts +1 -1
  29. package/dist/ui/collection_editor/import/CollectionEditorImportMapping.d.ts +7 -0
  30. package/dist/{components → ui}/collection_editor/import/clean_import_data.d.ts +1 -1
  31. package/dist/{components → ui}/collection_editor/properties/BlockPropertyField.d.ts +4 -1
  32. package/dist/{components → ui}/collection_editor/properties/CommonPropertyFields.d.ts +1 -0
  33. package/dist/{components → ui}/collection_editor/properties/MapPropertyField.d.ts +4 -1
  34. package/dist/{components → ui}/collection_editor/properties/RepeatPropertyField.d.ts +4 -1
  35. package/dist/{components → ui}/collection_editor/properties/StringPropertyField.d.ts +1 -1
  36. package/dist/ui/collection_editor/properties/UrlPropertyField.d.ts +4 -0
  37. package/dist/ui/collection_editor/templates/blog_template.d.ts +2 -0
  38. package/dist/ui/collection_editor/templates/pages_template.d.ts +2 -0
  39. package/dist/ui/collection_editor/templates/products_template.d.ts +2 -0
  40. package/dist/ui/collection_editor/templates/users_template.d.ts +2 -0
  41. package/dist/ui/collection_editor/utils/strings.d.ts +1 -0
  42. package/dist/ui/collection_editor/utils/supported_fields.d.ts +3 -0
  43. package/dist/ui/collection_editor/utils/update_property_for_widget.d.ts +2 -0
  44. package/dist/useCollectionEditorPlugin.d.ts +5 -3
  45. package/dist/utils/entities.d.ts +3 -4
  46. package/package.json +22 -19
  47. package/src/ConfigControllerProvider.tsx +336 -0
  48. package/src/index.ts +35 -0
  49. package/src/types/collection_editor_controller.tsx +42 -0
  50. package/src/types/collection_inference.ts +3 -0
  51. package/src/types/config_controller.tsx +60 -0
  52. package/src/types/config_permissions.ts +20 -0
  53. package/src/types/persisted_collection.ts +9 -0
  54. package/src/ui/CollectionViewHeaderAction.tsx +43 -0
  55. package/src/ui/EditorCollectionAction.tsx +109 -0
  56. package/src/ui/HomePageEditorCollectionAction.tsx +84 -0
  57. package/src/ui/MissingReferenceWidget.tsx +35 -0
  58. package/src/ui/NewCollectionButton.tsx +16 -0
  59. package/src/ui/NewCollectionCard.tsx +47 -0
  60. package/src/ui/PropertyAddColumnComponent.tsx +42 -0
  61. package/src/ui/RootCollectionSuggestions.tsx +55 -0
  62. package/src/ui/collection_editor/CollectionDetailsForm.tsx +366 -0
  63. package/src/ui/collection_editor/CollectionEditorDialog.tsx +754 -0
  64. package/src/ui/collection_editor/CollectionEditorWelcomeView.tsx +206 -0
  65. package/src/ui/collection_editor/CollectionPropertiesEditorForm.tsx +481 -0
  66. package/src/ui/collection_editor/CollectionYupValidation.tsx +7 -0
  67. package/src/ui/collection_editor/EntityCustomViewsSelectDialog.tsx +37 -0
  68. package/src/ui/collection_editor/EnumForm.tsx +354 -0
  69. package/src/ui/collection_editor/GetCodeDialog.tsx +110 -0
  70. package/src/ui/collection_editor/PropertyEditView.tsx +558 -0
  71. package/src/ui/collection_editor/PropertyFieldPreview.tsx +203 -0
  72. package/src/ui/collection_editor/PropertySelectItem.tsx +32 -0
  73. package/src/ui/collection_editor/PropertyTree.tsx +233 -0
  74. package/src/ui/collection_editor/SubcollectionsEditTab.tsx +253 -0
  75. package/src/ui/collection_editor/UnsavedChangesDialog.tsx +47 -0
  76. package/src/ui/collection_editor/import/CollectionEditorImportDataPreview.tsx +37 -0
  77. package/src/ui/collection_editor/import/CollectionEditorImportMapping.tsx +260 -0
  78. package/src/ui/collection_editor/import/clean_import_data.ts +53 -0
  79. package/src/ui/collection_editor/properties/BlockPropertyField.tsx +135 -0
  80. package/src/ui/collection_editor/properties/BooleanPropertyField.tsx +36 -0
  81. package/src/ui/collection_editor/properties/CommonPropertyFields.tsx +137 -0
  82. package/src/ui/collection_editor/properties/DateTimePropertyField.tsx +87 -0
  83. package/src/ui/collection_editor/properties/EnumPropertyField.tsx +117 -0
  84. package/src/ui/collection_editor/properties/FieldHelperView.tsx +13 -0
  85. package/src/ui/collection_editor/properties/KeyValuePropertyField.tsx +20 -0
  86. package/src/ui/collection_editor/properties/MapPropertyField.tsx +149 -0
  87. package/src/ui/collection_editor/properties/NumberPropertyField.tsx +38 -0
  88. package/src/ui/collection_editor/properties/ReferencePropertyField.tsx +165 -0
  89. package/src/ui/collection_editor/properties/RepeatPropertyField.tsx +108 -0
  90. package/src/ui/collection_editor/properties/StoragePropertyField.tsx +194 -0
  91. package/src/ui/collection_editor/properties/StringPropertyField.tsx +79 -0
  92. package/src/ui/collection_editor/properties/UrlPropertyField.tsx +89 -0
  93. package/src/ui/collection_editor/properties/advanced/AdvancedPropertyValidation.tsx +36 -0
  94. package/src/ui/collection_editor/properties/validation/ArrayPropertyValidation.tsx +50 -0
  95. package/src/ui/collection_editor/properties/validation/GeneralPropertyValidation.tsx +50 -0
  96. package/src/ui/collection_editor/properties/validation/NumberPropertyValidation.tsx +100 -0
  97. package/src/ui/collection_editor/properties/validation/StringPropertyValidation.tsx +132 -0
  98. package/src/ui/collection_editor/properties/validation/ValidationPanel.tsx +28 -0
  99. package/src/ui/collection_editor/templates/blog_template.ts +115 -0
  100. package/src/ui/collection_editor/templates/pages_template.ts +188 -0
  101. package/src/ui/collection_editor/templates/products_template.ts +88 -0
  102. package/src/ui/collection_editor/templates/users_template.ts +42 -0
  103. package/src/ui/collection_editor/util.ts +21 -0
  104. package/src/ui/collection_editor/utils/strings.ts +8 -0
  105. package/src/ui/collection_editor/utils/supported_fields.tsx +29 -0
  106. package/src/ui/collection_editor/utils/update_property_for_widget.ts +271 -0
  107. package/src/ui/collection_editor/utils/useTraceUpdate.tsx +23 -0
  108. package/src/useCollectionEditorController.tsx +9 -0
  109. package/src/useCollectionEditorPlugin.tsx +137 -0
  110. package/src/useCollectionsConfigController.tsx +9 -0
  111. package/src/utils/arrays.ts +3 -0
  112. package/src/utils/entities.ts +38 -0
  113. package/src/vite-env.d.ts +1 -0
  114. package/dist/components/collection_editor/PropertySelectItem.d.ts +0 -8
  115. package/dist/components/collection_editor/SelectIcons.d.ts +0 -6
  116. package/dist/components/collection_editor/import/CollectionEditorImportMapping.d.ts +0 -4
  117. package/dist/components/collection_editor/templates/blog_template.d.ts +0 -10
  118. package/dist/components/collection_editor/templates/products_template.d.ts +0 -12
  119. package/dist/components/collection_editor/templates/users_template.d.ts +0 -7
  120. package/dist/components/collection_editor/utils/supported_fields.d.ts +0 -3
  121. package/dist/components/collection_editor/utils/update_property_for_widget.d.ts +0 -3
  122. package/dist/types/editable_properties.d.ts +0 -10
  123. package/dist/utils/icons.d.ts +0 -2
  124. package/dist/utils/synonyms.d.ts +0 -1951
  125. /package/dist/{components → ui}/HomePageEditorCollectionAction.d.ts +0 -0
  126. /package/dist/{components → ui}/NewCollectionCard.d.ts +0 -0
  127. /package/dist/{components → ui}/collection_editor/CollectionEditorWelcomeView.d.ts +0 -0
  128. /package/dist/{components → ui}/collection_editor/EnumForm.d.ts +0 -0
  129. /package/dist/{components → ui}/collection_editor/UnsavedChangesDialog.d.ts +0 -0
  130. /package/dist/{components → ui}/collection_editor/properties/BooleanPropertyField.d.ts +0 -0
  131. /package/dist/{components → ui}/collection_editor/properties/DateTimePropertyField.d.ts +0 -0
  132. /package/dist/{components → ui}/collection_editor/properties/EnumPropertyField.d.ts +0 -0
  133. /package/dist/{components → ui}/collection_editor/properties/FieldHelperView.d.ts +0 -0
  134. /package/dist/{components → ui}/collection_editor/properties/KeyValuePropertyField.d.ts +0 -0
  135. /package/dist/{components → ui}/collection_editor/properties/NumberPropertyField.d.ts +0 -0
  136. /package/dist/{components → ui}/collection_editor/properties/ReferencePropertyField.d.ts +0 -0
  137. /package/dist/{components → ui}/collection_editor/properties/StoragePropertyField.d.ts +0 -0
  138. /package/dist/{components → ui}/collection_editor/properties/advanced/AdvancedPropertyValidation.d.ts +0 -0
  139. /package/dist/{components → ui}/collection_editor/properties/validation/ArrayPropertyValidation.d.ts +0 -0
  140. /package/dist/{components → ui}/collection_editor/properties/validation/GeneralPropertyValidation.d.ts +0 -0
  141. /package/dist/{components → ui}/collection_editor/properties/validation/NumberPropertyValidation.d.ts +0 -0
  142. /package/dist/{components → ui}/collection_editor/properties/validation/StringPropertyValidation.d.ts +0 -0
  143. /package/dist/{components → ui}/collection_editor/properties/validation/ValidationPanel.d.ts +0 -0
  144. /package/dist/{components → ui}/collection_editor/util.d.ts +0 -0
  145. /package/dist/{components → ui}/collection_editor/utils/useTraceUpdate.d.ts +0 -0
@@ -0,0 +1,336 @@
1
+ import React, { PropsWithChildren, useCallback, useEffect } from "react";
2
+ import equal from "react-fast-compare"
3
+
4
+ import { CollectionsConfigController } from "./types/config_controller";
5
+ import {
6
+ Property,
7
+ useCustomizationController,
8
+ useNavigationController,
9
+ User,
10
+ useSnackbarController
11
+ } from "@firecms/core";
12
+ import { CollectionEditorDialog } from "./ui/collection_editor/CollectionEditorDialog";
13
+ import { useNavigate } from "react-router";
14
+ import { CollectionEditorController } from "./types/collection_editor_controller";
15
+ import { CollectionEditorPermissionsBuilder } from "./types/config_permissions";
16
+ import { CollectionInference } from "./types/collection_inference";
17
+ import { PropertyFormDialog } from "./ui/collection_editor/PropertyEditView";
18
+ import { PersistedCollection } from "./types/persisted_collection";
19
+
20
+ export const ConfigControllerContext = React.createContext<CollectionsConfigController>({} as any);
21
+ export const CollectionEditorContext = React.createContext<CollectionEditorController>({} as any);
22
+
23
+ export interface ConfigControllerProviderProps {
24
+ /**
25
+ * Controller for managing the collections' config.
26
+ */
27
+ collectionConfigController: CollectionsConfigController;
28
+
29
+ /**
30
+ * Callback used to infer the schema from the data.
31
+ */
32
+ collectionInference?: CollectionInference;
33
+
34
+ /**
35
+ * Use this builder to define the permissions for the configuration per user.
36
+ */
37
+ configPermissions?: CollectionEditorPermissionsBuilder;
38
+
39
+ /**
40
+ * Groups that cannot be used to create new collections.
41
+ */
42
+ reservedGroups?: string[];
43
+
44
+ extraView?: {
45
+ View: React.ComponentType<{
46
+ path: string
47
+ }>,
48
+ icon: React.ReactNode
49
+ };
50
+
51
+ pathSuggestions?: (path?: string) => Promise<string[]>;
52
+
53
+ getUser: (uid: string) => User | null
54
+
55
+ getData?: (path: string, parentPaths: string[]) => Promise<object[]>;
56
+
57
+ onAnalyticsEvent?: (event: string, params?: object) => void;
58
+
59
+ }
60
+
61
+ export const ConfigControllerProvider = React.memo(
62
+ function ConfigControllerProvider({
63
+ children,
64
+ collectionConfigController,
65
+ configPermissions,
66
+ reservedGroups,
67
+ collectionInference,
68
+ extraView,
69
+ pathSuggestions,
70
+ getUser,
71
+ getData,
72
+ onAnalyticsEvent
73
+ }: PropsWithChildren<ConfigControllerProviderProps>) {
74
+
75
+ const navigation = useNavigationController();
76
+ const navigate = useNavigate();
77
+ const snackbarController = useSnackbarController();
78
+ const { propertyConfigs } = useCustomizationController();
79
+
80
+ const {
81
+ collections
82
+ } = navigation;
83
+ const existingPaths = collections.map(col => col.path.trim().toLowerCase());
84
+
85
+ const [rootPathSuggestions, setRootPathSuggestions] = React.useState<string[] | undefined>();
86
+ useEffect(() => {
87
+ if (pathSuggestions) {
88
+ pathSuggestions().then((paths) => {
89
+ setRootPathSuggestions(paths.filter(p => !existingPaths.includes(p.trim().toLowerCase())));
90
+ });
91
+ }
92
+ }, [pathSuggestions]);
93
+
94
+ const [currentDialog, setCurrentDialog] = React.useState<{
95
+ isNewCollection: boolean,
96
+ parentCollection?: PersistedCollection,
97
+ editedCollectionPath?: string,
98
+ fullPath?: string,
99
+ parentCollectionIds: string[],
100
+ initialValues?: {
101
+ path?: string,
102
+ group?: string,
103
+ name?: string
104
+ },
105
+ redirect: boolean
106
+ }>();
107
+
108
+ const [currentPropertyDialog, setCurrentPropertyDialog] = React.useState<{
109
+ propertyKey?: string,
110
+ property?: Property,
111
+ namespace?: string,
112
+ parentCollection?: PersistedCollection,
113
+ currentPropertiesOrder?: string[],
114
+ editedCollectionPath: string,
115
+ fullPath?: string,
116
+ parentCollectionIds: string[],
117
+ collectionEditable: boolean;
118
+ }>();
119
+
120
+ const defaultConfigPermissions: CollectionEditorPermissionsBuilder = useCallback(() => ({
121
+ createCollections: true,
122
+ editCollections: true,
123
+ deleteCollections: true
124
+ }), []);
125
+
126
+ const editCollection = useCallback(({
127
+ path,
128
+ fullPath,
129
+ parentCollectionIds,
130
+ parentCollection
131
+ }: {
132
+ path?: string,
133
+ fullPath?: string,
134
+ parentCollectionIds: string[],
135
+ parentCollection?: PersistedCollection
136
+ }) => {
137
+ console.debug("edit collection", path, fullPath, parentCollectionIds, parentCollection);
138
+ onAnalyticsEvent?.("edit_collection", { path, fullPath });
139
+ setCurrentDialog({
140
+ editedCollectionPath: path,
141
+ fullPath,
142
+ parentCollectionIds,
143
+ isNewCollection: false,
144
+ parentCollection,
145
+ redirect: false
146
+ });
147
+ }, []);
148
+
149
+ const editProperty = useCallback(({
150
+ propertyKey,
151
+ property,
152
+ editedCollectionPath,
153
+ currentPropertiesOrder,
154
+ parentCollectionIds,
155
+ collection
156
+ }: {
157
+ propertyKey?: string,
158
+ property?: Property,
159
+ currentPropertiesOrder?: string[],
160
+ editedCollectionPath: string,
161
+ parentCollectionIds: string[],
162
+ collection: PersistedCollection,
163
+ }) => {
164
+ console.debug("edit property", propertyKey, property, editedCollectionPath, currentPropertiesOrder, parentCollectionIds, collection);
165
+ onAnalyticsEvent?.("edit_property", { propertyKey, editedCollectionPath });
166
+ // namespace is all the path until the last dot
167
+ const namespace = propertyKey && propertyKey.includes(".")
168
+ ? propertyKey.substring(0, propertyKey.lastIndexOf("."))
169
+ : undefined;
170
+ const propertyKeyWithoutNamespace = propertyKey && propertyKey.includes(".")
171
+ ? propertyKey.substring(propertyKey.lastIndexOf(".") + 1)
172
+ : propertyKey;
173
+ setCurrentPropertyDialog({
174
+ propertyKey: propertyKeyWithoutNamespace,
175
+ property,
176
+ namespace,
177
+ currentPropertiesOrder,
178
+ editedCollectionPath,
179
+ parentCollectionIds,
180
+ collectionEditable: collection?.editable ?? false
181
+ });
182
+ }, []);
183
+
184
+ const createCollection = React.useCallback(({
185
+ parentCollectionIds,
186
+ parentCollection,
187
+ initialValues,
188
+ redirect
189
+ }: {
190
+ parentCollectionIds: string[],
191
+ parentCollection?: PersistedCollection
192
+ initialValues?: {
193
+ group?: string,
194
+ path?: string,
195
+ name?: string
196
+ },
197
+ redirect: boolean
198
+ }) => {
199
+ console.debug("create collection", parentCollectionIds, parentCollection, initialValues, redirect);
200
+ onAnalyticsEvent?.("create_collection", { parentCollectionIds, parentCollection, initialValues, redirect });
201
+ setCurrentDialog({
202
+ isNewCollection: true,
203
+ parentCollectionIds,
204
+ parentCollection,
205
+ initialValues,
206
+ redirect
207
+ });
208
+ }, []);
209
+
210
+ const getPathSuggestions = !pathSuggestions
211
+ ? undefined
212
+ : (path?: string) => {
213
+ if (!path && rootPathSuggestions)
214
+ return Promise.resolve(rootPathSuggestions);
215
+ else {
216
+ return pathSuggestions?.(path);
217
+ }
218
+ }
219
+
220
+ return (
221
+ <ConfigControllerContext.Provider value={collectionConfigController}>
222
+ <CollectionEditorContext.Provider
223
+ value={{
224
+ editCollection,
225
+ createCollection,
226
+ editProperty,
227
+ configPermissions: configPermissions ?? defaultConfigPermissions,
228
+ rootPathSuggestions
229
+ }}>
230
+
231
+ {children}
232
+
233
+ <CollectionEditorDialog
234
+ open={Boolean(currentDialog)}
235
+ configController={collectionConfigController}
236
+ isNewCollection={false}
237
+ collectionInference={collectionInference}
238
+ {...currentDialog}
239
+ getData={getData}
240
+ reservedGroups={reservedGroups}
241
+ extraView={extraView}
242
+ pathSuggestions={getPathSuggestions}
243
+ getUser={getUser}
244
+ handleClose={(collection) => {
245
+ if (currentDialog?.redirect) {
246
+ if (collection && currentDialog?.isNewCollection && !currentDialog.parentCollectionIds.length) {
247
+ const url = navigation.buildUrlCollectionPath(collection.id ?? collection.path);
248
+ navigate(url);
249
+ }
250
+ }
251
+ setCurrentDialog(undefined);
252
+ }}/>
253
+
254
+ {/* Used for editing properties*/}
255
+ <PropertyFormDialog
256
+ open={Boolean(currentPropertyDialog)}
257
+ includeIdAndName={true}
258
+ existingProperty={Boolean(currentPropertyDialog?.propertyKey)}
259
+ autoUpdateId={!currentPropertyDialog ? false : !currentPropertyDialog?.propertyKey}
260
+ autoOpenTypeSelect={!currentPropertyDialog ? false : !currentPropertyDialog?.propertyKey}
261
+ inArray={false}
262
+ collectionEditable={currentPropertyDialog?.collectionEditable ?? false}
263
+ getData={getData && currentPropertyDialog?.editedCollectionPath
264
+ ? () => {
265
+ console.debug("get data for property", currentPropertyDialog?.editedCollectionPath);
266
+ const resolvedPath = navigation.resolveAliasesFrom(currentPropertyDialog.editedCollectionPath!)
267
+ return getData(resolvedPath, []);
268
+ }
269
+ : undefined}
270
+ onPropertyChanged={({
271
+ id,
272
+ property
273
+ }) => {
274
+ if (!currentPropertyDialog) return;
275
+ if (!id) return;
276
+ const newProperty = !(currentPropertyDialog.propertyKey);
277
+ return collectionConfigController.saveProperty({
278
+ path: currentPropertyDialog?.editedCollectionPath,
279
+ property,
280
+ propertyKey: id,
281
+ newPropertiesOrder: newProperty && currentPropertyDialog.currentPropertiesOrder ? [...currentPropertyDialog.currentPropertiesOrder, id] : undefined,
282
+ namespace: currentPropertyDialog.namespace,
283
+ parentCollectionIds: currentPropertyDialog.parentCollectionIds
284
+ })
285
+ .catch((e) => {
286
+ console.error(e);
287
+ snackbarController.open({
288
+ type: "error",
289
+ message: "Error persisting property: " + (e.message ?? "Details in the console")
290
+ });
291
+ return false;
292
+ });
293
+ }}
294
+ onPropertyChangedImmediate={false}
295
+ onDelete={() => {
296
+ if (!currentPropertyDialog?.propertyKey) return;
297
+ const newPropertiesOrder = currentPropertyDialog?.currentPropertiesOrder?.filter(p => p !== currentPropertyDialog?.propertyKey);
298
+ return collectionConfigController.deleteProperty({
299
+ path: currentPropertyDialog?.editedCollectionPath,
300
+ propertyKey: currentPropertyDialog?.propertyKey,
301
+ namespace: currentPropertyDialog?.namespace,
302
+ newPropertiesOrder,
303
+ parentCollectionIds: currentPropertyDialog?.parentCollectionIds
304
+ })
305
+ .then(() => {
306
+ setCurrentPropertyDialog(undefined);
307
+ }).catch((e) => {
308
+ console.error(e);
309
+ snackbarController.open({
310
+ type: "error",
311
+ message: "Error deleting property: " + (e.message ?? "Details in the console")
312
+ });
313
+ return false;
314
+ });
315
+ }}
316
+ onError={() => {
317
+ }}
318
+ onOkClicked={() => {
319
+ setCurrentPropertyDialog(undefined);
320
+ }}
321
+ onCancel={() => {
322
+ setCurrentPropertyDialog(undefined);
323
+ }}
324
+ initialErrors={{}}
325
+ forceShowErrors={false}
326
+ existingPropertyKeys={[]}
327
+ allowDataInference={true}
328
+ propertyConfigs={propertyConfigs}
329
+ property={currentPropertyDialog?.property}
330
+ propertyKey={currentPropertyDialog?.propertyKey}/>
331
+
332
+ </CollectionEditorContext.Provider>
333
+
334
+ </ConfigControllerContext.Provider>
335
+ );
336
+ }, equal);
package/src/index.ts ADDED
@@ -0,0 +1,35 @@
1
+ export {
2
+ useCollectionEditorPlugin
3
+ } from "./useCollectionEditorPlugin";
4
+
5
+ export {
6
+ useCollectionEditorController
7
+ } from "./useCollectionEditorController";
8
+ export {
9
+ useCollectionsConfigController
10
+ } from "./useCollectionsConfigController";
11
+
12
+ export {
13
+ editableProperty, removeNonEditableProperties
14
+ } from "./utils/entities";
15
+
16
+ export type {
17
+ CollectionsConfigController, DeleteCollectionParams, SaveCollectionParams, UpdateCollectionParams
18
+ } from "./types/config_controller";
19
+ export type {
20
+ CollectionEditorController
21
+ } from "./types/collection_editor_controller";
22
+ export type {
23
+ CollectionEditorPermissions, CollectionEditorPermissionsBuilder
24
+ } from "./types/config_permissions";
25
+ export type {
26
+ PersistedCollection
27
+ } from "./types/persisted_collection";
28
+
29
+ export type {
30
+ CollectionInference
31
+ } from "./types/collection_inference";
32
+
33
+ export { MissingReferenceWidget } from "./ui/MissingReferenceWidget";
34
+
35
+ export * from "./ui/collection_editor/util";
@@ -0,0 +1,42 @@
1
+ import { CollectionEditorPermissionsBuilder } from "./config_permissions";
2
+ import { Property } from "@firecms/core";
3
+ import { PersistedCollection } from "./persisted_collection";
4
+
5
+ /**
6
+ * Controller to open the collection editor dialog.
7
+ * @group Hooks and utilities
8
+ */
9
+ export interface CollectionEditorController {
10
+
11
+ editCollection: (props: {
12
+ path?: string,
13
+ fullPath?: string,
14
+ parentCollectionIds: string[],
15
+ parentCollection?: PersistedCollection
16
+ }) => void;
17
+
18
+ createCollection: (props: {
19
+ initialValues?: {
20
+ group?: string,
21
+ path?: string,
22
+ name?: string
23
+ },
24
+ parentCollectionIds: string[],
25
+ parentCollection?: PersistedCollection,
26
+ redirect: boolean
27
+ }) => void;
28
+
29
+ editProperty: (props: {
30
+ propertyKey?: string,
31
+ property?: Property,
32
+ currentPropertiesOrder?: string[],
33
+ editedCollectionPath: string,
34
+ parentCollectionIds: string[],
35
+ collection: PersistedCollection
36
+ }) => void;
37
+
38
+ configPermissions: CollectionEditorPermissionsBuilder;
39
+
40
+ rootPathSuggestions?: string[];
41
+
42
+ }
@@ -0,0 +1,3 @@
1
+ import { EntityCollection } from "@firecms/core";
2
+
3
+ export type CollectionInference = (path: string, collectionGroup: boolean, parentCollectionIds: string[]) => Promise<Partial<EntityCollection> | null>;
@@ -0,0 +1,60 @@
1
+ import { CMSType, Property } from "@firecms/core";
2
+ import { PersistedCollection } from "./persisted_collection";
3
+
4
+ /**
5
+ * Use this controller to access the configuration that is stored externally,
6
+ * and not defined in code.
7
+ */
8
+ export interface CollectionsConfigController {
9
+
10
+ loading: boolean;
11
+
12
+ collections?: PersistedCollection[];
13
+
14
+ getCollection: (id: string) => PersistedCollection;
15
+
16
+ saveCollection: <M extends { [Key: string]: CMSType }>(params: SaveCollectionParams<M>) => Promise<void>;
17
+ updateCollection: <M extends { [Key: string]: CMSType }>(params: UpdateCollectionParams<M>) => Promise<void>;
18
+
19
+ saveProperty: (params: SavePropertyParams) => Promise<void>;
20
+ deleteProperty: (params: DeletePropertyParams) => Promise<void>;
21
+
22
+ deleteCollection: (props: DeleteCollectionParams) => Promise<void>;
23
+
24
+ }
25
+
26
+ export type UpdateCollectionParams<M extends Record<string, any>> = {
27
+ id: string,
28
+ collectionData: Partial<PersistedCollection<M>>,
29
+ previousPath?: string,
30
+ parentCollectionIds?: string[]
31
+ }
32
+
33
+ export type SaveCollectionParams<M extends Record<string, any>> = {
34
+ id: string,
35
+ collectionData: PersistedCollection<M>,
36
+ previousPath?: string,
37
+ parentCollectionIds?: string[]
38
+ }
39
+
40
+ export type SavePropertyParams = {
41
+ path: string,
42
+ propertyKey: string,
43
+ namespace?: string,
44
+ newPropertiesOrder?: string[],
45
+ property: Property,
46
+ parentCollectionIds?: string[]
47
+ }
48
+
49
+ export type DeletePropertyParams = {
50
+ path: string,
51
+ propertyKey: string,
52
+ namespace?: string,
53
+ newPropertiesOrder?: string[],
54
+ parentCollectionIds?: string[]
55
+ }
56
+
57
+ export type DeleteCollectionParams = {
58
+ path: string,
59
+ parentCollectionIds?: string[]
60
+ }
@@ -0,0 +1,20 @@
1
+ import { EntityCollection } from "@firecms/core";
2
+
3
+ export type CollectionEditorPermissionsBuilder<UserType = any, EC extends EntityCollection = EntityCollection> = (params: { user: UserType | null, collection?: EC }) => CollectionEditorPermissions;
4
+
5
+ export type CollectionEditorPermissions = {
6
+ /**
7
+ * Is the user allowed to create new collections.
8
+ */
9
+ createCollections: boolean;
10
+
11
+ /**
12
+ * Is the user allowed to modify this collection
13
+ */
14
+ editCollections: boolean;
15
+
16
+ /**
17
+ * Is the user allowed to delete this collection
18
+ */
19
+ deleteCollections: boolean;
20
+ }
@@ -0,0 +1,9 @@
1
+ import { EntityCollection, User } from "@firecms/core";
2
+
3
+ export type PersistedCollection<M extends Record<string, any> = any, UserType extends User = User>
4
+ = Omit<EntityCollection<M, UserType>, "subcollections"> & {
5
+ // properties: Properties<M>;
6
+ ownerId: string;
7
+ subcollections?: PersistedCollection<any, any>[];
8
+ editable?: boolean;
9
+ }
@@ -0,0 +1,43 @@
1
+ import { ResolvedProperty } from "@firecms/core";
2
+ import { IconButton, SettingsIcon, Tooltip } from "@firecms/ui";
3
+ import React from "react";
4
+ import { useCollectionEditorController } from "../useCollectionEditorController";
5
+ import { PersistedCollection } from "../types/persisted_collection";
6
+
7
+ export function CollectionViewHeaderAction({
8
+ propertyKey,
9
+ onHover,
10
+ property,
11
+ fullPath,
12
+ parentCollectionIds,
13
+ collection
14
+ }: {
15
+ property: ResolvedProperty,
16
+ propertyKey: string,
17
+ onHover: boolean,
18
+ fullPath: string,
19
+ parentCollectionIds: string[],
20
+ collection: PersistedCollection;
21
+ }) {
22
+
23
+ const collectionEditorController = useCollectionEditorController();
24
+
25
+ return (
26
+ <Tooltip title={"Edit"}>
27
+ <IconButton
28
+ className={onHover ? "bg-white dark:bg-gray-950" : "hidden"}
29
+ onClick={() => {
30
+ collectionEditorController.editProperty({
31
+ propertyKey,
32
+ property,
33
+ editedCollectionPath: fullPath,
34
+ parentCollectionIds,
35
+ collection
36
+ });
37
+ }}
38
+ size={"small"}>
39
+ <SettingsIcon size={"small"}/>
40
+ </IconButton>
41
+ </Tooltip>
42
+ )
43
+ }
@@ -0,0 +1,109 @@
1
+ import equal from "react-fast-compare"
2
+
3
+ import {
4
+ CollectionActionsProps,
5
+ mergeDeep,
6
+ useAuthController,
7
+ useNavigationController,
8
+ useSnackbarController
9
+ } from "@firecms/core";
10
+ import { Button, IconButton, SaveIcon, SettingsIcon, Tooltip, UndoIcon, } from "@firecms/ui";
11
+
12
+ import { useCollectionEditorController } from "../useCollectionEditorController";
13
+ import { useCollectionsConfigController } from "../useCollectionsConfigController";
14
+ import { PersistedCollection } from "../types/persisted_collection";
15
+
16
+ export function EditorCollectionAction({
17
+ path: fullPath,
18
+ parentCollectionIds,
19
+ collection,
20
+ tableController
21
+ }: CollectionActionsProps) {
22
+
23
+ const authController = useAuthController();
24
+ const navigationController = useNavigationController();
25
+ const collectionEditorController = useCollectionEditorController();
26
+ const configController = useCollectionsConfigController();
27
+ const snackbarController = useSnackbarController();
28
+
29
+ const parentCollection = navigationController.getCollectionFromIds(parentCollectionIds);
30
+
31
+ const canEditCollection = collectionEditorController.configPermissions
32
+ ? collectionEditorController.configPermissions({
33
+ user: authController.user,
34
+ collection
35
+ }).editCollections
36
+ : true;
37
+
38
+ let saveDefaultFilterButton = null;
39
+ if (!equal(getObjectOrNull(tableController.filterValues), getObjectOrNull(collection.initialFilter)) ||
40
+ !equal(getObjectOrNull(tableController.sortBy), getObjectOrNull(collection.initialSort))) {
41
+ saveDefaultFilterButton = <>
42
+ {collection.initialFilter || collection.initialSort && <Tooltip
43
+ title={"Reset to default filter and sort"}>
44
+ <Button
45
+ color={"primary"}
46
+ size={"small"}
47
+ variant={"text"}
48
+ onClick={() => {
49
+ tableController.clearFilter?.();
50
+ if (collection?.initialFilter)
51
+ tableController.setFilterValues?.(collection?.initialFilter);
52
+ if (collection?.initialSort)
53
+ tableController.setSortBy?.(collection?.initialSort);
54
+ }}>
55
+ <UndoIcon/>
56
+ </Button>
57
+ </Tooltip>}
58
+
59
+ <Tooltip
60
+ title={tableController.sortBy || tableController.filterValues ? "Save default filter and sort" : "Clear default filter and sort"}>
61
+ <Button
62
+ color={"primary"}
63
+ size={"small"}
64
+ variant={"outlined"}
65
+ onClick={() => configController
66
+ ?.saveCollection({
67
+ id: collection.path,
68
+ parentCollectionIds,
69
+ collectionData: mergeDeep(collection as PersistedCollection,
70
+ {
71
+ initialFilter: tableController.filterValues ?? null,
72
+ initialSort: tableController.sortBy ?? null
73
+ })
74
+ }).then(() => {
75
+ snackbarController.open({
76
+ type: "success",
77
+ message: "Default config saved"
78
+ });
79
+ })}>
80
+ <SaveIcon/>
81
+ </Button>
82
+ </Tooltip>
83
+ </>;
84
+ }
85
+
86
+ const editorButton = <Tooltip
87
+ title={canEditCollection ? "Edit collection" : "You don't have permissions to edit this collection"}>
88
+ <IconButton
89
+ color={"primary"}
90
+ disabled={!canEditCollection}
91
+ onClick={canEditCollection
92
+ ? () => collectionEditorController?.editCollection({ path: collection.path, fullPath, parentCollectionIds, parentCollection: parentCollection as PersistedCollection })
93
+ : undefined}>
94
+ <SettingsIcon/>
95
+ </IconButton>
96
+ </Tooltip>;
97
+
98
+ return <>
99
+ {canEditCollection && saveDefaultFilterButton}
100
+ {editorButton}
101
+ </>
102
+
103
+ }
104
+
105
+ function getObjectOrNull(o?: object): object | null {
106
+ if (o && Object.keys(o).length === 0)
107
+ return o
108
+ return o ?? null;
109
+ }