@firecms/collection_editor 3.0.0-canary.99 → 3.0.0-rc.2
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.
- package/README.md +165 -1
- package/dist/ConfigControllerProvider.d.ts +0 -1
- package/dist/index.es.js +9957 -4962
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +9949 -4958
- package/dist/index.umd.js.map +1 -1
- package/dist/types/collection_editor_controller.d.ts +0 -1
- package/dist/types/collection_inference.d.ts +4 -1
- package/dist/types/config_controller.d.ts +3 -1
- package/dist/types/config_permissions.d.ts +2 -2
- package/dist/types/persisted_collection.d.ts +1 -1
- package/dist/ui/EditorEntityAction.d.ts +2 -0
- package/dist/ui/collection_editor/CollectionDetailsForm.d.ts +3 -1
- package/dist/ui/collection_editor/CollectionEditorWelcomeView.d.ts +1 -1
- package/dist/ui/collection_editor/CollectionPropertiesEditorForm.d.ts +1 -1
- package/dist/ui/collection_editor/EntityActionsEditTab.d.ts +4 -0
- package/dist/ui/collection_editor/EntityActionsSelectDialog.d.ts +4 -0
- package/dist/ui/collection_editor/LayoutModeSwitch.d.ts +5 -0
- package/dist/ui/collection_editor/PropertyEditView.d.ts +8 -0
- package/dist/ui/collection_editor/PropertyTree.d.ts +2 -3
- package/dist/ui/collection_editor/import/CollectionEditorImportDataPreview.d.ts +1 -1
- package/dist/ui/collection_editor/import/CollectionEditorImportMapping.d.ts +8 -1
- package/dist/ui/collection_editor/import/clean_import_data.d.ts +1 -1
- package/dist/ui/collection_editor/properties/MarkdownPropertyField.d.ts +4 -0
- package/dist/ui/collection_editor/properties/ReferencePropertyField.d.ts +2 -1
- package/dist/ui/collection_editor/properties/StringPropertyField.d.ts +1 -1
- package/dist/useCollectionEditorPlugin.d.ts +6 -6
- package/dist/utils/collections.d.ts +1 -1
- package/package.json +25 -23
- package/src/ConfigControllerProvider.tsx +3 -8
- package/src/types/collection_editor_controller.tsx +1 -2
- package/src/types/collection_inference.ts +4 -1
- package/src/types/config_controller.tsx +4 -1
- package/src/types/config_permissions.ts +1 -1
- package/src/types/persisted_collection.ts +2 -3
- package/src/ui/CollectionViewHeaderAction.tsx +4 -2
- package/src/ui/EditorCollectionAction.tsx +3 -7
- package/src/ui/EditorCollectionActionStart.tsx +1 -1
- package/src/ui/EditorEntityAction.tsx +51 -0
- package/src/ui/HomePageEditorCollectionAction.tsx +5 -3
- package/src/ui/NewCollectionButton.tsx +1 -1
- package/src/ui/PropertyAddColumnComponent.tsx +5 -3
- package/src/ui/collection_editor/CollectionDetailsForm.tsx +126 -49
- package/src/ui/collection_editor/CollectionEditorDialog.tsx +71 -16
- package/src/ui/collection_editor/CollectionEditorWelcomeView.tsx +20 -29
- package/src/ui/collection_editor/CollectionPropertiesEditorForm.tsx +19 -17
- package/src/ui/collection_editor/EntityActionsEditTab.tsx +163 -0
- package/src/ui/collection_editor/EntityActionsSelectDialog.tsx +41 -0
- package/src/ui/collection_editor/EntityCustomViewsSelectDialog.tsx +11 -7
- package/src/ui/collection_editor/EnumForm.tsx +11 -7
- package/src/ui/collection_editor/GetCodeDialog.tsx +46 -26
- package/src/ui/collection_editor/LayoutModeSwitch.tsx +54 -0
- package/src/ui/collection_editor/PropertyEditView.tsx +263 -76
- package/src/ui/collection_editor/PropertyFieldPreview.tsx +7 -6
- package/src/ui/collection_editor/PropertyTree.tsx +184 -138
- package/src/ui/collection_editor/SubcollectionsEditTab.tsx +24 -17
- package/src/ui/collection_editor/UnsavedChangesDialog.tsx +9 -7
- package/src/ui/collection_editor/import/CollectionEditorImportDataPreview.tsx +20 -4
- package/src/ui/collection_editor/import/CollectionEditorImportMapping.tsx +34 -3
- package/src/ui/collection_editor/import/clean_import_data.ts +1 -1
- package/src/ui/collection_editor/properties/BlockPropertyField.tsx +18 -12
- package/src/ui/collection_editor/properties/DateTimePropertyField.tsx +54 -47
- package/src/ui/collection_editor/properties/EnumPropertyField.tsx +2 -0
- package/src/ui/collection_editor/properties/MapPropertyField.tsx +3 -2
- package/src/ui/collection_editor/properties/MarkdownPropertyField.tsx +139 -0
- package/src/ui/collection_editor/properties/ReferencePropertyField.tsx +7 -3
- package/src/ui/collection_editor/properties/StoragePropertyField.tsx +13 -18
- package/src/ui/collection_editor/properties/StringPropertyField.tsx +4 -9
- package/src/ui/collection_editor/properties/UrlPropertyField.tsx +1 -0
- package/src/ui/collection_editor/properties/advanced/AdvancedPropertyValidation.tsx +2 -0
- package/src/ui/collection_editor/properties/validation/ValidationPanel.tsx +2 -2
- package/src/ui/collection_editor/templates/pages_template.ts +1 -6
- package/src/ui/collection_editor/utils/strings.ts +13 -6
- package/src/ui/collection_editor/utils/supported_fields.tsx +2 -0
- package/src/ui/collection_editor/utils/update_property_for_widget.ts +37 -6
- package/src/useCollectionEditorPlugin.tsx +20 -15
- package/src/utils/collections.ts +23 -7
- package/dist/ui/collection_editor/PropertySelectItem.d.ts +0 -8
- package/src/ui/collection_editor/PropertySelectItem.tsx +0 -32
|
@@ -5,7 +5,7 @@ import { Button, Card, Chip, CircularProgress, cls, Container, Icon, Tooltip, Ty
|
|
|
5
5
|
import { productsCollectionTemplate } from "./templates/products_template";
|
|
6
6
|
import { blogCollectionTemplate } from "./templates/blog_template";
|
|
7
7
|
import { usersCollectionTemplate } from "./templates/users_template";
|
|
8
|
-
import { ImportFileUpload } from "@firecms/
|
|
8
|
+
import { ImportFileUpload } from "@firecms/data_import";
|
|
9
9
|
import { pagesCollectionTemplate } from "./templates/pages_template";
|
|
10
10
|
import { useFormex } from "@firecms/formex";
|
|
11
11
|
|
|
@@ -37,18 +37,6 @@ export function CollectionEditorWelcomeView({
|
|
|
37
37
|
}
|
|
38
38
|
}, [existingCollectionPaths, path, pathSuggestions]);
|
|
39
39
|
|
|
40
|
-
// const {
|
|
41
|
-
// values,
|
|
42
|
-
// setFieldValue,
|
|
43
|
-
// setValues,
|
|
44
|
-
// handleChange,
|
|
45
|
-
// touched,
|
|
46
|
-
// errors,
|
|
47
|
-
// setFieldTouched,
|
|
48
|
-
// isSubmitting,
|
|
49
|
-
// submitCount
|
|
50
|
-
// } = useFormex<EntityCollection>();
|
|
51
|
-
|
|
52
40
|
const {
|
|
53
41
|
values,
|
|
54
42
|
setFieldValue,
|
|
@@ -56,6 +44,11 @@ export function CollectionEditorWelcomeView({
|
|
|
56
44
|
submitCount
|
|
57
45
|
} = useFormex<EntityCollection>();
|
|
58
46
|
|
|
47
|
+
const noSuggestions = !loadingPathSuggestions && (filteredPathSuggestions ?? [])?.length === 0;
|
|
48
|
+
if (!noSuggestions) {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
|
|
59
52
|
return (
|
|
60
53
|
<div className={"overflow-auto my-auto"}>
|
|
61
54
|
<Container maxWidth={"4xl"} className={"flex flex-col gap-4 p-8 m-auto"}>
|
|
@@ -96,12 +89,11 @@ export function CollectionEditorWelcomeView({
|
|
|
96
89
|
{suggestion}
|
|
97
90
|
</Chip>
|
|
98
91
|
))}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
}
|
|
92
|
+
{(filteredPathSuggestions ?? []).length === 0 && !loadingPathSuggestions && <Typography
|
|
93
|
+
variant={"caption"}
|
|
94
|
+
color={"secondary"}>
|
|
95
|
+
No existing paths found
|
|
96
|
+
</Typography>}
|
|
105
97
|
|
|
106
98
|
</div>
|
|
107
99
|
|
|
@@ -116,28 +108,29 @@ export function CollectionEditorWelcomeView({
|
|
|
116
108
|
<div className={"flex gap-4"}>
|
|
117
109
|
<TemplateButton title={"Products"}
|
|
118
110
|
subtitle={"A collection of products with images, prices and stock"}
|
|
119
|
-
icon={<Icon size={"small"}
|
|
111
|
+
icon={<Icon size={"small"}
|
|
112
|
+
iconKey={productsCollectionTemplate.icon! as string}/>}
|
|
120
113
|
onClick={() => {
|
|
121
114
|
setValues(productsCollectionTemplate);
|
|
122
115
|
onContinue();
|
|
123
116
|
}}/>
|
|
124
117
|
<TemplateButton title={"Users"}
|
|
125
118
|
subtitle={"A collection of users with emails, names and roles"}
|
|
126
|
-
icon={<Icon size={"small"} iconKey={usersCollectionTemplate.icon!}/>}
|
|
119
|
+
icon={<Icon size={"small"} iconKey={usersCollectionTemplate.icon! as string}/>}
|
|
127
120
|
onClick={() => {
|
|
128
121
|
setValues(usersCollectionTemplate);
|
|
129
122
|
onContinue();
|
|
130
123
|
}}/>
|
|
131
124
|
<TemplateButton title={"Blog posts"}
|
|
132
125
|
subtitle={"A collection of blog posts with images, authors and complex content"}
|
|
133
|
-
icon={<Icon size={"small"} iconKey={blogCollectionTemplate.icon!}/>}
|
|
126
|
+
icon={<Icon size={"small"} iconKey={blogCollectionTemplate.icon! as string}/>}
|
|
134
127
|
onClick={() => {
|
|
135
128
|
setValues(blogCollectionTemplate);
|
|
136
129
|
onContinue();
|
|
137
130
|
}}/>
|
|
138
131
|
<TemplateButton title={"Pages"}
|
|
139
132
|
subtitle={"A collection of pages with images, authors and complex content"}
|
|
140
|
-
icon={<Icon size={"small"} iconKey={pagesCollectionTemplate.icon!}/>}
|
|
133
|
+
icon={<Icon size={"small"} iconKey={pagesCollectionTemplate.icon! as string}/>}
|
|
141
134
|
onClick={() => {
|
|
142
135
|
setValues(pagesCollectionTemplate);
|
|
143
136
|
onContinue();
|
|
@@ -185,14 +178,15 @@ export function TemplateButton({
|
|
|
185
178
|
}) {
|
|
186
179
|
|
|
187
180
|
return (
|
|
188
|
-
<Tooltip title={subtitle}
|
|
181
|
+
<Tooltip title={subtitle}
|
|
182
|
+
asChild={true}>
|
|
189
183
|
<Card
|
|
190
184
|
onClick={onClick}
|
|
191
185
|
className={cls(
|
|
192
186
|
"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",
|
|
193
|
-
"text-
|
|
187
|
+
"text-surface-700 dark:text-surface-accent-300",
|
|
194
188
|
"hover:border-primary-dark hover:text-primary-dark dark:hover:text-primary focus:ring-primary hover:ring-1 hover:ring-primary",
|
|
195
|
-
"border-
|
|
189
|
+
"border-surface-400 dark:border-surface-600 "
|
|
196
190
|
)}
|
|
197
191
|
>
|
|
198
192
|
{icon}
|
|
@@ -201,9 +195,6 @@ export function TemplateButton({
|
|
|
201
195
|
<Typography variant={"subtitle1"}>
|
|
202
196
|
{title}
|
|
203
197
|
</Typography>
|
|
204
|
-
{/*<Typography>*/}
|
|
205
|
-
{/* {subtitle}*/}
|
|
206
|
-
{/*</Typography>*/}
|
|
207
198
|
|
|
208
199
|
</div>
|
|
209
200
|
</Card>
|
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
} from "@firecms/core";
|
|
17
17
|
import {
|
|
18
18
|
AddIcon,
|
|
19
|
-
|
|
19
|
+
AutorenewIcon,
|
|
20
20
|
Button,
|
|
21
21
|
CircularProgress,
|
|
22
22
|
cls,
|
|
@@ -24,7 +24,6 @@ import {
|
|
|
24
24
|
DebouncedTextField,
|
|
25
25
|
defaultBorderMixin,
|
|
26
26
|
IconButton,
|
|
27
|
-
Paper,
|
|
28
27
|
Tooltip,
|
|
29
28
|
Typography,
|
|
30
29
|
} from "@firecms/ui";
|
|
@@ -45,7 +44,7 @@ type CollectionEditorFormProps = {
|
|
|
45
44
|
extraIcon: React.ReactNode;
|
|
46
45
|
getUser?: (uid: string) => User | null;
|
|
47
46
|
getData?: () => Promise<object[]>;
|
|
48
|
-
doCollectionInference
|
|
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) => {
|
|
@@ -233,12 +234,6 @@ export function CollectionPropertiesEditorForm({
|
|
|
233
234
|
|
|
234
235
|
// If the id has changed we need to a little cleanup
|
|
235
236
|
if (previousId && previousId !== id) {
|
|
236
|
-
console.debug("onPropertyChanged, id change", {
|
|
237
|
-
id,
|
|
238
|
-
property,
|
|
239
|
-
previousId,
|
|
240
|
-
namespace
|
|
241
|
-
})
|
|
242
237
|
|
|
243
238
|
const previousFullId = getFullId(previousId, namespace);
|
|
244
239
|
const previousPropertyPath = idToPropertiesPath(previousFullId);
|
|
@@ -316,8 +311,9 @@ export function CollectionPropertiesEditorForm({
|
|
|
316
311
|
};
|
|
317
312
|
|
|
318
313
|
const body = (
|
|
319
|
-
<div className={"grid grid-cols-12 gap-2 h-full bg-
|
|
314
|
+
<div className={"grid grid-cols-12 gap-2 h-full bg-white dark:bg-surface-950"}>
|
|
320
315
|
<div className={cls(
|
|
316
|
+
"bg-surface-50 dark:bg-surface-900",
|
|
321
317
|
"p-4 md:p-8 pb-20 md:pb-20",
|
|
322
318
|
"col-span-12 lg:col-span-5 h-full overflow-auto",
|
|
323
319
|
!asDialog && "border-r " + defaultBorderMixin
|
|
@@ -351,7 +347,8 @@ export function CollectionPropertiesEditorForm({
|
|
|
351
347
|
</div>}
|
|
352
348
|
|
|
353
349
|
<div className="ml-1 mt-2 flex flex-row gap-2">
|
|
354
|
-
<Tooltip title={"Get the code for this collection"}
|
|
350
|
+
<Tooltip title={"Get the code for this collection"}
|
|
351
|
+
asChild={true}>
|
|
355
352
|
<IconButton
|
|
356
353
|
variant={"filled"}
|
|
357
354
|
disabled={inferringProperties}
|
|
@@ -359,17 +356,20 @@ export function CollectionPropertiesEditorForm({
|
|
|
359
356
|
<CodeIcon/>
|
|
360
357
|
</IconButton>
|
|
361
358
|
</Tooltip>
|
|
362
|
-
{inferPropertiesFromData && <Tooltip title={"Add new properties based on data"}
|
|
359
|
+
{inferPropertiesFromData && <Tooltip title={"Add new properties based on data"}
|
|
360
|
+
asChild={true}>
|
|
363
361
|
<IconButton
|
|
364
362
|
variant={"filled"}
|
|
365
363
|
disabled={inferringProperties}
|
|
366
364
|
onClick={inferPropertiesFromData}>
|
|
367
|
-
{inferringProperties ? <CircularProgress size={"small"}/> : <
|
|
365
|
+
{inferringProperties ? <CircularProgress size={"small"}/> : <AutorenewIcon/>}
|
|
368
366
|
</IconButton>
|
|
369
367
|
</Tooltip>}
|
|
370
|
-
<Tooltip title={"Add new property"}
|
|
368
|
+
<Tooltip title={"Add new property"}
|
|
369
|
+
asChild={true}>
|
|
371
370
|
<Button
|
|
372
371
|
variant={"outlined"}
|
|
372
|
+
color={"primary"}
|
|
373
373
|
onClick={() => setNewPropertyDialogOpen(true)}>
|
|
374
374
|
<AddIcon/>
|
|
375
375
|
</Button>
|
|
@@ -404,8 +404,8 @@ export function CollectionPropertiesEditorForm({
|
|
|
404
404
|
|
|
405
405
|
{!asDialog &&
|
|
406
406
|
<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"}>
|
|
407
|
-
<
|
|
408
|
-
className="sticky top-8
|
|
407
|
+
<div
|
|
408
|
+
className="sticky top-8 min-h-full w-full flex flex-col justify-center">
|
|
409
409
|
|
|
410
410
|
{selectedPropertyFullId &&
|
|
411
411
|
selectedProperty &&
|
|
@@ -438,6 +438,7 @@ export function CollectionPropertiesEditorForm({
|
|
|
438
438
|
: "Select a property to edit it"}
|
|
439
439
|
</Typography>
|
|
440
440
|
<Button variant={"outlined"}
|
|
441
|
+
color={"primary"}
|
|
441
442
|
onClick={() => setNewPropertyDialogOpen(true)}
|
|
442
443
|
>
|
|
443
444
|
<AddIcon/>
|
|
@@ -449,7 +450,7 @@ export function CollectionPropertiesEditorForm({
|
|
|
449
450
|
<Typography variant={"label"} className="flex items-center justify-center">
|
|
450
451
|
{"This property is defined as a property builder in code"}
|
|
451
452
|
</Typography>}
|
|
452
|
-
</
|
|
453
|
+
</div>
|
|
453
454
|
</div>}
|
|
454
455
|
|
|
455
456
|
{asDialog && <PropertyFormDialog
|
|
@@ -471,6 +472,7 @@ export function CollectionPropertiesEditorForm({
|
|
|
471
472
|
getData={getData}
|
|
472
473
|
propertyConfigs={propertyConfigs}
|
|
473
474
|
collectionEditable={collectionEditable}
|
|
475
|
+
onCancel={closePropertyDialog}
|
|
474
476
|
onOkClicked={asDialog
|
|
475
477
|
? closePropertyDialog
|
|
476
478
|
: undefined
|
|
@@ -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({
|
|
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"}
|
|
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
|
-
|
|
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 = (
|
|
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={"
|
|
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
|
-
|
|
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 && <
|
|
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>
|