@firecms/collection_editor 3.0.0-canary.29 → 3.0.0-canary.290

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 (94) hide show
  1. package/LICENSE +114 -21
  2. package/README.md +165 -1
  3. package/dist/ConfigControllerProvider.d.ts +1 -2
  4. package/dist/index.d.ts +1 -0
  5. package/dist/index.es.js +11093 -4800
  6. package/dist/index.es.js.map +1 -1
  7. package/dist/index.umd.js +11750 -3
  8. package/dist/index.umd.js.map +1 -1
  9. package/dist/types/collection_editor_controller.d.ts +3 -2
  10. package/dist/types/collection_inference.d.ts +4 -1
  11. package/dist/types/config_controller.d.ts +3 -1
  12. package/dist/types/config_permissions.d.ts +2 -2
  13. package/dist/types/persisted_collection.d.ts +1 -1
  14. package/dist/ui/CollectionViewHeaderAction.d.ts +3 -2
  15. package/dist/ui/EditorCollectionActionStart.d.ts +2 -0
  16. package/dist/ui/EditorEntityAction.d.ts +2 -0
  17. package/dist/ui/PropertyAddColumnComponent.d.ts +3 -1
  18. package/dist/ui/collection_editor/CollectionDetailsForm.d.ts +3 -1
  19. package/dist/ui/collection_editor/CollectionEditorDialog.d.ts +3 -2
  20. package/dist/ui/collection_editor/CollectionEditorWelcomeView.d.ts +2 -2
  21. package/dist/ui/collection_editor/CollectionPropertiesEditorForm.d.ts +2 -2
  22. package/dist/ui/collection_editor/EntityActionsEditTab.d.ts +4 -0
  23. package/dist/ui/collection_editor/EntityActionsSelectDialog.d.ts +4 -0
  24. package/dist/ui/collection_editor/LayoutModeSwitch.d.ts +5 -0
  25. package/dist/ui/collection_editor/PropertyEditView.d.ts +8 -0
  26. package/dist/ui/collection_editor/PropertyTree.d.ts +11 -12
  27. package/dist/ui/collection_editor/SubcollectionsEditTab.d.ts +1 -1
  28. package/dist/ui/collection_editor/import/CollectionEditorImportDataPreview.d.ts +1 -1
  29. package/dist/ui/collection_editor/import/CollectionEditorImportMapping.d.ts +8 -1
  30. package/dist/ui/collection_editor/import/clean_import_data.d.ts +1 -1
  31. package/dist/ui/collection_editor/properties/MarkdownPropertyField.d.ts +4 -0
  32. package/dist/ui/collection_editor/properties/ReferencePropertyField.d.ts +2 -1
  33. package/dist/ui/collection_editor/properties/StringPropertyField.d.ts +1 -1
  34. package/dist/useCollectionEditorPlugin.d.ts +8 -11
  35. package/dist/utils/collections.d.ts +6 -0
  36. package/dist/utils/entities.d.ts +1 -1
  37. package/package.json +25 -37
  38. package/src/ConfigControllerProvider.tsx +64 -66
  39. package/src/index.ts +1 -0
  40. package/src/types/collection_editor_controller.tsx +6 -5
  41. package/src/types/collection_inference.ts +4 -1
  42. package/src/types/config_controller.tsx +4 -1
  43. package/src/types/config_permissions.ts +1 -1
  44. package/src/types/persisted_collection.ts +2 -3
  45. package/src/ui/CollectionViewHeaderAction.tsx +10 -5
  46. package/src/ui/EditorCollectionAction.tsx +12 -70
  47. package/src/ui/EditorCollectionActionStart.tsx +87 -0
  48. package/src/ui/EditorEntityAction.tsx +51 -0
  49. package/src/ui/HomePageEditorCollectionAction.tsx +21 -14
  50. package/src/ui/NewCollectionButton.tsx +1 -1
  51. package/src/ui/NewCollectionCard.tsx +3 -3
  52. package/src/ui/PropertyAddColumnComponent.tsx +11 -6
  53. package/src/ui/collection_editor/CollectionDetailsForm.tsx +170 -50
  54. package/src/ui/collection_editor/CollectionEditorDialog.tsx +119 -39
  55. package/src/ui/collection_editor/CollectionEditorWelcomeView.tsx +24 -33
  56. package/src/ui/collection_editor/CollectionPropertiesEditorForm.tsx +46 -49
  57. package/src/ui/collection_editor/EntityActionsEditTab.tsx +163 -0
  58. package/src/ui/collection_editor/EntityActionsSelectDialog.tsx +41 -0
  59. package/src/ui/collection_editor/EntityCustomViewsSelectDialog.tsx +11 -7
  60. package/src/ui/collection_editor/EnumForm.tsx +11 -7
  61. package/src/ui/collection_editor/GetCodeDialog.tsx +60 -28
  62. package/src/ui/collection_editor/LayoutModeSwitch.tsx +54 -0
  63. package/src/ui/collection_editor/PropertyEditView.tsx +269 -81
  64. package/src/ui/collection_editor/PropertyFieldPreview.tsx +12 -14
  65. package/src/ui/collection_editor/PropertyTree.tsx +184 -138
  66. package/src/ui/collection_editor/SubcollectionsEditTab.tsx +26 -19
  67. package/src/ui/collection_editor/UnsavedChangesDialog.tsx +9 -7
  68. package/src/ui/collection_editor/import/CollectionEditorImportDataPreview.tsx +41 -9
  69. package/src/ui/collection_editor/import/CollectionEditorImportMapping.tsx +43 -10
  70. package/src/ui/collection_editor/import/clean_import_data.ts +1 -1
  71. package/src/ui/collection_editor/properties/BlockPropertyField.tsx +32 -20
  72. package/src/ui/collection_editor/properties/DateTimePropertyField.tsx +54 -47
  73. package/src/ui/collection_editor/properties/EnumPropertyField.tsx +3 -1
  74. package/src/ui/collection_editor/properties/MapPropertyField.tsx +8 -7
  75. package/src/ui/collection_editor/properties/MarkdownPropertyField.tsx +139 -0
  76. package/src/ui/collection_editor/properties/ReferencePropertyField.tsx +7 -3
  77. package/src/ui/collection_editor/properties/RepeatPropertyField.tsx +0 -1
  78. package/src/ui/collection_editor/properties/StoragePropertyField.tsx +165 -20
  79. package/src/ui/collection_editor/properties/StringPropertyField.tsx +4 -9
  80. package/src/ui/collection_editor/properties/UrlPropertyField.tsx +1 -0
  81. package/src/ui/collection_editor/properties/advanced/AdvancedPropertyValidation.tsx +2 -0
  82. package/src/ui/collection_editor/properties/validation/ValidationPanel.tsx +3 -3
  83. package/src/ui/collection_editor/templates/blog_template.ts +1 -4
  84. package/src/ui/collection_editor/templates/pages_template.ts +1 -6
  85. package/src/ui/collection_editor/utils/strings.ts +13 -6
  86. package/src/ui/collection_editor/utils/supported_fields.tsx +2 -0
  87. package/src/ui/collection_editor/utils/update_property_for_widget.ts +37 -6
  88. package/src/useCollectionEditorPlugin.tsx +38 -32
  89. package/src/utils/collections.ts +45 -0
  90. package/src/utils/entities.ts +6 -9
  91. package/dist/ui/RootCollectionSuggestions.d.ts +0 -3
  92. package/dist/ui/collection_editor/PropertySelectItem.d.ts +0 -8
  93. package/src/ui/RootCollectionSuggestions.tsx +0 -63
  94. package/src/ui/collection_editor/PropertySelectItem.tsx +0 -32
@@ -1,4 +1,4 @@
1
- import React, { useCallback, useEffect, useMemo, useState } from "react";
1
+ import React, { useEffect, useMemo, useState } from "react";
2
2
 
3
3
  import { Field, getIn, useFormex } from "@firecms/formex";
4
4
  import {
@@ -16,15 +16,14 @@ import {
16
16
  } from "@firecms/core";
17
17
  import {
18
18
  AddIcon,
19
- AutoAwesomeIcon,
19
+ AutorenewIcon,
20
20
  Button,
21
21
  CircularProgress,
22
- cn,
22
+ cls,
23
23
  CodeIcon,
24
24
  DebouncedTextField,
25
25
  defaultBorderMixin,
26
26
  IconButton,
27
- Paper,
28
27
  Tooltip,
29
28
  Typography,
30
29
  } from "@firecms/ui";
@@ -43,9 +42,9 @@ type CollectionEditorFormProps = {
43
42
  setDirty?: (dirty: boolean) => void;
44
43
  reservedGroups?: string[];
45
44
  extraIcon: React.ReactNode;
46
- getUser: (uid: string) => User | null;
45
+ getUser?: (uid: string) => User | null;
47
46
  getData?: () => Promise<object[]>;
48
- doCollectionInference: (collection: PersistedCollection) => Promise<Partial<EntityCollection> | null> | undefined;
47
+ doCollectionInference?: (collection: PersistedCollection) => Promise<Partial<EntityCollection> | null> | undefined;
49
48
  propertyConfigs: Record<string, PropertyConfig>;
50
49
  collectionEditable: boolean;
51
50
  };
@@ -108,6 +107,8 @@ export function CollectionPropertiesEditorForm({
108
107
  return;
109
108
 
110
109
  setInferringProperties(true);
110
+
111
+ console.debug("CollectionEditor: inferring properties from data", doCollectionInference, values);
111
112
  // @ts-ignore
112
113
  doCollectionInference(values)
113
114
  .then((newCollection) => {
@@ -158,20 +159,20 @@ export function CollectionPropertiesEditorForm({
158
159
  }
159
160
  : undefined;
160
161
 
161
- const getCurrentPropertiesOrder = useCallback((namespace?: string) => {
162
- if (!namespace) return currentPropertiesOrderRef.current[""];
162
+ const getCurrentPropertiesOrder = (namespace?: string) => {
163
+ if (!namespace) return currentPropertiesOrderRef.current[""] ?? getIn(values, namespaceToPropertiesOrderPath());
163
164
  return currentPropertiesOrderRef.current[namespace] ?? getIn(values, namespaceToPropertiesOrderPath(namespace));
164
- }, [values]);
165
+ };
165
166
 
166
- const updatePropertiesOrder = useCallback((newPropertiesOrder: string[], namespace?: string) => {
167
+ const updatePropertiesOrder = (newPropertiesOrder: string[], namespace?: string) => {
167
168
  const propertiesOrderPath = namespaceToPropertiesOrderPath(namespace);
168
169
 
169
170
  setFieldValue(propertiesOrderPath, newPropertiesOrder, false);
170
171
  currentPropertiesOrderRef.current[namespace ?? ""] = newPropertiesOrder;
171
172
 
172
- }, [setFieldValue]);
173
+ };
173
174
 
174
- const deleteProperty = useCallback((propertyKey?: string, namespace?: string) => {
175
+ const deleteProperty = (propertyKey?: string, namespace?: string) => {
175
176
  const fullId = propertyKey ? getFullId(propertyKey, namespace) : undefined;
176
177
  if (!fullId)
177
178
  throw Error("collection editor miss config");
@@ -179,15 +180,17 @@ export function CollectionPropertiesEditorForm({
179
180
  setFieldValue(idToPropertiesPath(fullId), undefined, false);
180
181
 
181
182
  const currentPropertiesOrder = getCurrentPropertiesOrder(namespace);
182
- const newPropertiesOrder = currentPropertiesOrder.filter((p) => p !== propertyKey);
183
- updatePropertiesOrder(newPropertiesOrder, namespace);
183
+ if (currentPropertiesOrder) {
184
+ const newPropertiesOrder = currentPropertiesOrder.filter((p) => p !== propertyKey);
185
+ updatePropertiesOrder(newPropertiesOrder, namespace);
186
+ }
184
187
 
185
188
  setNewPropertyDialogOpen(false);
186
189
 
187
190
  setSelectedPropertyIndex(undefined);
188
191
  setSelectedPropertyKey(undefined);
189
192
  setSelectedPropertyNamespace(undefined);
190
- }, [getCurrentPropertiesOrder, setFieldValue, updatePropertiesOrder]);
193
+ };
191
194
 
192
195
  const onPropertyMove = (propertiesOrder: string[], namespace?: string) => {
193
196
  setFieldValue(namespaceToPropertiesOrderPath(namespace), propertiesOrder, false);
@@ -207,8 +210,8 @@ export function CollectionPropertiesEditorForm({
207
210
  ...(values.properties ?? {}),
208
211
  [id]: property
209
212
  }, false);
210
- const newPropertiesOrder = [...(values.propertiesOrder ?? Object.keys(values.properties)), id];
211
213
 
214
+ const newPropertiesOrder = [...(values.propertiesOrder ?? Object.keys(values.properties)), id];
212
215
  updatePropertiesOrder(newPropertiesOrder);
213
216
 
214
217
  setNewPropertyDialogOpen(false);
@@ -231,12 +234,6 @@ export function CollectionPropertiesEditorForm({
231
234
 
232
235
  // If the id has changed we need to a little cleanup
233
236
  if (previousId && previousId !== id) {
234
- console.debug("onPropertyChanged, id change", {
235
- id,
236
- property,
237
- previousId,
238
- namespace
239
- })
240
237
 
241
238
  const previousFullId = getFullId(previousId, namespace);
242
239
  const previousPropertyPath = idToPropertiesPath(previousFullId);
@@ -258,14 +255,6 @@ export function CollectionPropertiesEditorForm({
258
255
  setFieldTouched(previousPropertyPath, false, false);
259
256
  }
260
257
 
261
- console.debug("onPropertyChanged", {
262
- id,
263
- property,
264
- previousId,
265
- namespace,
266
- propertyPath
267
- })
268
-
269
258
  if (propertyPath) {
270
259
  setFieldValue(propertyPath, property, false);
271
260
  setFieldTouched(propertyPath, true, false);
@@ -273,7 +262,7 @@ export function CollectionPropertiesEditorForm({
273
262
 
274
263
  };
275
264
 
276
- const onPropertyErrorInternal = useCallback((id: string, namespace?: string, error?: Record<string, any>) => {
265
+ const onPropertyErrorInternal = (id: string, namespace?: string, error?: Record<string, any>) => {
277
266
  const propertyPath = id ? getFullId(id, namespace) : undefined;
278
267
  console.debug("onPropertyErrorInternal", {
279
268
  id,
@@ -286,7 +275,7 @@ export function CollectionPropertiesEditorForm({
286
275
  onPropertyError(id, namespace, hasError ? error : undefined);
287
276
  setFieldError(idToPropertiesPath(propertyPath), hasError ? "Property error" : undefined);
288
277
  }
289
- }, [])
278
+ }
290
279
 
291
280
  const closePropertyDialog = () => {
292
281
  setSelectedPropertyIndex(undefined);
@@ -301,9 +290,9 @@ export function CollectionPropertiesEditorForm({
301
290
  ? values.propertiesOrder
302
291
  : Object.keys(values.properties)) as string[];
303
292
 
304
- const owner = useMemo(() => values.ownerId ? getUser(values.ownerId) : null, [getUser, values.ownerId]);
293
+ const owner = useMemo(() => values.ownerId && getUser ? getUser(values.ownerId) : null, [getUser, values.ownerId]);
305
294
 
306
- const onPropertyClick = useCallback((propertyKey: string, namespace?: string) => {
295
+ const onPropertyClick = (propertyKey: string, namespace?: string) => {
307
296
  console.debug("CollectionEditor: onPropertyClick", {
308
297
  propertyKey,
309
298
  namespace
@@ -311,11 +300,12 @@ export function CollectionPropertiesEditorForm({
311
300
  setSelectedPropertyIndex(usedPropertiesOrder.indexOf(propertyKey));
312
301
  setSelectedPropertyKey(propertyKey);
313
302
  setSelectedPropertyNamespace(namespace);
314
- }, [usedPropertiesOrder]);
303
+ };
315
304
 
316
305
  const body = (
317
- <div className={"grid grid-cols-12 gap-2 h-full bg-gray-50 dark:bg-gray-900"}>
318
- <div className={cn(
306
+ <div className={"grid grid-cols-12 gap-2 h-full bg-white dark:bg-surface-950"}>
307
+ <div className={cls(
308
+ "bg-surface-50 dark:bg-surface-900",
319
309
  "p-4 md:p-8 pb-20 md:pb-20",
320
310
  "col-span-12 lg:col-span-5 h-full overflow-auto",
321
311
  !asDialog && "border-r " + defaultBorderMixin
@@ -349,7 +339,8 @@ export function CollectionPropertiesEditorForm({
349
339
  </div>}
350
340
 
351
341
  <div className="ml-1 mt-2 flex flex-row gap-2">
352
- <Tooltip title={"Get the code for this collection"}>
342
+ <Tooltip title={"Get the code for this collection"}
343
+ asChild={true}>
353
344
  <IconButton
354
345
  variant={"filled"}
355
346
  disabled={inferringProperties}
@@ -357,17 +348,20 @@ export function CollectionPropertiesEditorForm({
357
348
  <CodeIcon/>
358
349
  </IconButton>
359
350
  </Tooltip>
360
- {inferPropertiesFromData && <Tooltip title={"Add new properties based on data"}>
351
+ {inferPropertiesFromData && <Tooltip title={"Add new properties based on data"}
352
+ asChild={true}>
361
353
  <IconButton
362
354
  variant={"filled"}
363
355
  disabled={inferringProperties}
364
356
  onClick={inferPropertiesFromData}>
365
- {inferringProperties ? <CircularProgress size={"small"}/> : <AutoAwesomeIcon/>}
357
+ {inferringProperties ? <CircularProgress size={"small"}/> : <AutorenewIcon/>}
366
358
  </IconButton>
367
359
  </Tooltip>}
368
- <Tooltip title={"Add new property"}>
360
+ <Tooltip title={"Add new property"}
361
+ asChild={true}>
369
362
  <Button
370
363
  variant={"outlined"}
364
+ color={"primary"}
371
365
  onClick={() => setNewPropertyDialogOpen(true)}>
372
366
  <AddIcon/>
373
367
  </Button>
@@ -402,8 +396,8 @@ export function CollectionPropertiesEditorForm({
402
396
 
403
397
  {!asDialog &&
404
398
  <div className={"col-span-12 lg:col-span-7 p-4 md:py-8 md:px-4 h-full overflow-auto pb-20 md:pb-20"}>
405
- <Paper
406
- className="sticky top-8 p-4 min-h-full border border-transparent w-full flex flex-col justify-center ">
399
+ <div
400
+ className="sticky top-8 min-h-full w-full flex flex-col justify-center">
407
401
 
408
402
  {selectedPropertyFullId &&
409
403
  selectedProperty &&
@@ -436,6 +430,7 @@ export function CollectionPropertiesEditorForm({
436
430
  : "Select a property to edit it"}
437
431
  </Typography>
438
432
  <Button variant={"outlined"}
433
+ color={"primary"}
439
434
  onClick={() => setNewPropertyDialogOpen(true)}
440
435
  >
441
436
  <AddIcon/>
@@ -447,7 +442,7 @@ export function CollectionPropertiesEditorForm({
447
442
  <Typography variant={"label"} className="flex items-center justify-center">
448
443
  {"This property is defined as a property builder in code"}
449
444
  </Typography>}
450
- </Paper>
445
+ </div>
451
446
  </div>}
452
447
 
453
448
  {asDialog && <PropertyFormDialog
@@ -469,6 +464,7 @@ export function CollectionPropertiesEditorForm({
469
464
  getData={getData}
470
465
  propertyConfigs={propertyConfigs}
471
466
  collectionEditable={collectionEditable}
467
+ onCancel={closePropertyDialog}
472
468
  onOkClicked={asDialog
473
469
  ? closePropertyDialog
474
470
  : undefined
@@ -496,11 +492,12 @@ export function CollectionPropertiesEditorForm({
496
492
  collectionEditable={collectionEditable}
497
493
  existingPropertyKeys={values.propertiesOrder as string[]}/>
498
494
 
499
- <GetCodeDialog
500
- collection={values}
501
- open={codeDialogOpen}
502
- onOpenChange={setCodeDialogOpen}/>
503
-
495
+ <ErrorBoundary>
496
+ <GetCodeDialog
497
+ collection={values}
498
+ open={codeDialogOpen}
499
+ onOpenChange={setCodeDialogOpen}/>
500
+ </ErrorBoundary>
504
501
  </>
505
502
  );
506
503
  }
@@ -0,0 +1,163 @@
1
+ import React from "react";
2
+ import {
3
+ ConfirmationDialog,
4
+ EntityAction,
5
+ EntityCollection,
6
+ resolveEntityAction,
7
+ useCustomizationController
8
+ } from "@firecms/core";
9
+ import {
10
+ AddIcon,
11
+ Alert,
12
+ Button,
13
+ Container,
14
+ DeleteIcon,
15
+ IconButton,
16
+ Paper,
17
+ Table,
18
+ TableBody,
19
+ TableCell,
20
+ TableRow,
21
+ Tooltip,
22
+ Typography,
23
+ } from "@firecms/ui";
24
+ import { PersistedCollection } from "../../types/persisted_collection";
25
+ import { useFormex } from "@firecms/formex";
26
+ import { EntityActionsSelectDialog } from "./EntityActionsSelectDialog";
27
+
28
+ export function EntityActionsEditTab({
29
+ collection,
30
+ }: {
31
+ collection: PersistedCollection,
32
+ }) {
33
+
34
+ const { entityActions: contextEntityActions } = useCustomizationController();
35
+
36
+ const [addEntityActionDialogOpen, setAddEntityActionDialogOpen] = React.useState<boolean>(false);
37
+ const [actionToDelete, setActionToDelete] = React.useState<string | undefined>();
38
+
39
+ const {
40
+ values,
41
+ setFieldValue
42
+ } = useFormex<EntityCollection>();
43
+
44
+ const resolvedEntityActions = values.entityActions?.filter((e): e is string => typeof e === "string")
45
+ .map(e => resolveEntityAction(e, contextEntityActions))
46
+ .filter(Boolean) as EntityAction<any>[] ?? [];
47
+ const hardCodedEntityActions = collection.entityActions?.filter((e): e is EntityAction<any> => typeof e !== "string") ?? [];
48
+ const totalEntityActions = resolvedEntityActions.length + hardCodedEntityActions.length;
49
+
50
+ return (
51
+ <div className={"overflow-auto my-auto"}>
52
+ <Container maxWidth={"2xl"} className={"flex flex-col gap-4 p-8 m-auto"}>
53
+ <div className={"flex flex-col gap-16"}>
54
+ <div className={"flex-grow flex flex-col gap-4 items-start"}>
55
+ <Typography variant={"h5"}>
56
+ Custom actions
57
+ </Typography>
58
+
59
+ {totalEntityActions === 0 &&
60
+ <Alert action={<Button variant="text"
61
+ size={"small"}
62
+ href={"https://firecms.co/docs/custom_actions"}
63
+ component={"a"}
64
+ rel="noopener noreferrer"
65
+ target="_blank">More info</Button>}>
66
+ Define your own custom actions by uploading them with the CLI.
67
+ </Alert>
68
+ }
69
+
70
+ {<>
71
+ <Paper className={"flex flex-col gap-4 p-2 w-full"}>
72
+ <Table>
73
+ <TableBody>
74
+ {resolvedEntityActions.map((action) => (
75
+ <TableRow key={action.key}>
76
+ <TableCell
77
+ align="left">
78
+ <Typography variant={"subtitle2"} className={"flex-grow"}>
79
+ {action.name}
80
+ </Typography>
81
+ </TableCell>
82
+ <TableCell
83
+ align="right">
84
+ <Tooltip title={"Remove"}
85
+ asChild={true}>
86
+ <IconButton size="small"
87
+ onClick={(e) => {
88
+ e.preventDefault();
89
+ e.stopPropagation();
90
+ setActionToDelete(action.key);
91
+ }}
92
+ color="inherit">
93
+ <DeleteIcon size={"small"}/>
94
+ </IconButton>
95
+ </Tooltip>
96
+ </TableCell>
97
+ </TableRow>
98
+ ))}
99
+ {hardCodedEntityActions.map((action) => (
100
+ <TableRow key={action.key}>
101
+ <TableCell
102
+ align="left">
103
+ <Typography variant={"subtitle2"} className={"flex-grow"}>
104
+ {action.name}
105
+ </Typography>
106
+ <Typography variant={"caption"} className={"flex-grow"}>
107
+ This action is defined in code with
108
+ key <code>{action.key}</code>
109
+ </Typography>
110
+ </TableCell>
111
+ </TableRow>
112
+ ))}
113
+ </TableBody>
114
+ </Table>
115
+
116
+ <Button
117
+ onClick={() => {
118
+ setAddEntityActionDialogOpen(true);
119
+ }}
120
+ variant={"text"}
121
+ startIcon={<AddIcon/>}>
122
+ Add custom entity action
123
+ </Button>
124
+ </Paper>
125
+
126
+ </>}
127
+
128
+
129
+ </div>
130
+
131
+ </div>
132
+ </Container>
133
+
134
+ <div style={{ height: "52px" }}/>
135
+
136
+ {actionToDelete &&
137
+ <ConfirmationDialog open={Boolean(actionToDelete)}
138
+ onAccept={() => {
139
+ setFieldValue("entityActions", values.entityActions?.filter(e => e !== actionToDelete));
140
+ setActionToDelete(undefined);
141
+ }}
142
+ onCancel={() => setActionToDelete(undefined)}
143
+ title={<>Remove this action?</>}
144
+ body={<>This will <b>not
145
+ delete any data</b>, only
146
+ the action in the CMS</>}/>}
147
+
148
+ <EntityActionsSelectDialog
149
+ open={addEntityActionDialogOpen}
150
+ onClose={(selectedActionKey) => {
151
+ if (selectedActionKey) {
152
+ console.log("Selected action key:", selectedActionKey);
153
+ const value = [...(values.entityActions ?? []), selectedActionKey]
154
+ // only actions that are defined in the registry
155
+ .filter((e): e is string => typeof e === "string" && (contextEntityActions ?? []).some(action => action.key === e));
156
+ ;
157
+ setFieldValue("entityActions", value);
158
+ }
159
+ setAddEntityActionDialogOpen(false);
160
+ }}/>
161
+ </div>
162
+ );
163
+ }
@@ -0,0 +1,41 @@
1
+ import { useCustomizationController } from "@firecms/core";
2
+ import { Button, Dialog, DialogActions, DialogContent, DialogTitle, Typography } from "@firecms/ui";
3
+ import React from "react";
4
+
5
+ export function EntityActionsSelectDialog({
6
+ open,
7
+ onClose
8
+ }: { open: boolean, onClose: (selectedActionKey?: string) => void }) {
9
+ const {
10
+ entityActions
11
+ } = useCustomizationController();
12
+
13
+ return <Dialog
14
+ maxWidth={"md"}
15
+ open={open}>
16
+ <DialogTitle>Select custom action</DialogTitle>
17
+ <DialogContent className={"flex flex-col gap-4"}>
18
+ {entityActions?.map((action) => {
19
+ return <Button
20
+ key={action.key}
21
+ onClick={() => onClose(action.key)}
22
+ fullWidth
23
+ variant={"text"}
24
+ >
25
+ {action.name} ({action.key})
26
+ </Button>;
27
+ })}
28
+ {(entityActions ?? []).length === 0 &&
29
+ <Typography variant={"body2"}>
30
+ No custom actions defined. Define your custom actions in the customization settings, before using this
31
+ dialog.
32
+ </Typography>
33
+ }
34
+ </DialogContent>
35
+ <DialogActions>
36
+ <Button variant={"outlined"}
37
+ color={"primary"}
38
+ onClick={() => onClose()}>Cancel</Button>
39
+ </DialogActions>
40
+ </Dialog>
41
+ }
@@ -1,8 +1,11 @@
1
1
  import { useCustomizationController } from "@firecms/core";
2
- import { Button, Dialog, DialogActions, DialogContent, Typography } from "@firecms/ui";
2
+ import { Button, Dialog, DialogActions, DialogContent, DialogTitle, Typography } from "@firecms/ui";
3
3
  import React from "react";
4
4
 
5
- export function EntityCustomViewsSelectDialog({ open, onClose }: { open: boolean, onClose: (selectedViewKey?: string) => void }) {
5
+ export function EntityCustomViewsSelectDialog({
6
+ open,
7
+ onClose
8
+ }: { open: boolean, onClose: (selectedViewKey?: string) => void }) {
6
9
  const {
7
10
  entityViews,
8
11
  } = useCustomizationController();
@@ -10,10 +13,8 @@ export function EntityCustomViewsSelectDialog({ open, onClose }: { open: boolean
10
13
  return <Dialog
11
14
  maxWidth={"md"}
12
15
  open={open}>
16
+ <DialogTitle>Select custom view</DialogTitle>
13
17
  <DialogContent className={"flex flex-col gap-4"}>
14
- <Typography variant={"h6"}>
15
- Select view
16
- </Typography>
17
18
  {entityViews?.map((view) => {
18
19
  return <Button
19
20
  key={view.key}
@@ -26,12 +27,15 @@ export function EntityCustomViewsSelectDialog({ open, onClose }: { open: boolean
26
27
  })}
27
28
  {(entityViews ?? []).length === 0 &&
28
29
  <Typography variant={"body2"}>
29
- No custom views defined
30
+ No custom views defined. Define your custom views in the customization settings, before using this
31
+ dialog.
30
32
  </Typography>
31
33
  }
32
34
  </DialogContent>
33
35
  <DialogActions>
34
- <Button variant={"outlined"} onClick={() => onClose()}>Cancel</Button>
36
+ <Button variant={"outlined"}
37
+ color={"primary"}
38
+ onClick={() => onClose()}>Cancel</Button>
35
39
  </DialogActions>
36
40
  </Dialog>
37
41
  }
@@ -1,9 +1,9 @@
1
1
  import React, { useEffect } from "react";
2
2
  import equal from "react-fast-compare"
3
3
 
4
- import { ArrayContainer, EnumValueConfig, EnumValues, FieldCaption, } from "@firecms/core";
4
+ import { ArrayContainer, ArrayEntryParams, EnumValueConfig, EnumValues, FieldCaption, } from "@firecms/core";
5
5
  import {
6
- AutoAwesomeIcon,
6
+ AutorenewIcon,
7
7
  Badge,
8
8
  Button,
9
9
  CircularProgress,
@@ -11,6 +11,7 @@ import {
11
11
  Dialog,
12
12
  DialogActions,
13
13
  DialogContent,
14
+ DialogTitle,
14
15
  IconButton,
15
16
  ListIcon,
16
17
  Paper,
@@ -121,7 +122,10 @@ function EnumFormFields({
121
122
  const inferredValuesRef = React.useRef(new Set());
122
123
  const inferredValues = inferredValuesRef.current;
123
124
 
124
- const buildEntry = (index: number, internalId: number) => {
125
+ const buildEntry = ({
126
+ index,
127
+ internalId
128
+ }:ArrayEntryParams) => {
125
129
  const justAdded = lastInternalIdAdded === internalId;
126
130
  const entryError = errors?.enumValues && errors?.enumValues[index];
127
131
  return <EnumEntry index={index}
@@ -179,7 +183,7 @@ function EnumFormFields({
179
183
  variant={"text"}
180
184
  size={"small"}
181
185
  onClick={inferValues}>
182
- {inferring ? <CircularProgress size={"small"}/> : <AutoAwesomeIcon/>}
186
+ {inferring ? <CircularProgress size={"smallest"}/> : <AutorenewIcon/>}
183
187
  Infer values from data
184
188
  </Button>}
185
189
  </div>
@@ -193,7 +197,7 @@ function EnumFormFields({
193
197
  size={"small"}
194
198
  buildEntry={buildEntry}
195
199
  onInternalIdAdded={setLastInternalIdAdded}
196
- includeAddButton={true}
200
+ canAddElements={true}
197
201
  onValueChange={(value) => setFieldValue(enumValuesPath, value)}
198
202
  newDefaultEntry={{ id: "", label: "" }}/>
199
203
 
@@ -263,7 +267,7 @@ const EnumEntry = React.memo(
263
267
  size="small"
264
268
  autoFocus={autoFocus}
265
269
  autoComplete="off"
266
- endAdornment={inferredEntry && <AutoAwesomeIcon size={"small"}/>}
270
+ endAdornment={inferredEntry && <AutorenewIcon size={"small"}/>}
267
271
  error={Boolean(entryError?.label)}/>
268
272
 
269
273
  {!disabled &&
@@ -324,7 +328,7 @@ function EnumEntryDialog({
324
328
  open={open}
325
329
  onOpenChange={(open) => !open ? onClose() : undefined}
326
330
  >
327
-
331
+ <DialogTitle hidden>Enum form dialog</DialogTitle>
328
332
  <DialogContent>
329
333
  {index !== undefined &&
330
334
  <div>