@firecms/collection_editor 3.0.0-canary.26 → 3.0.0-canary.261
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/LICENSE +114 -21
- package/README.md +165 -1
- package/dist/ConfigControllerProvider.d.ts +1 -2
- package/dist/index.d.ts +1 -0
- package/dist/index.es.js +10742 -4787
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +11412 -3
- package/dist/index.umd.js.map +1 -1
- package/dist/types/collection_editor_controller.d.ts +3 -2
- 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/CollectionViewHeaderAction.d.ts +3 -2
- package/dist/ui/EditorCollectionActionStart.d.ts +2 -0
- package/dist/ui/EditorEntityAction.d.ts +2 -0
- package/dist/ui/PropertyAddColumnComponent.d.ts +3 -1
- package/dist/ui/collection_editor/CollectionDetailsForm.d.ts +3 -1
- package/dist/ui/collection_editor/CollectionEditorDialog.d.ts +3 -2
- package/dist/ui/collection_editor/CollectionEditorWelcomeView.d.ts +2 -2
- package/dist/ui/collection_editor/CollectionPropertiesEditorForm.d.ts +2 -2
- 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 +11 -12
- package/dist/ui/collection_editor/SubcollectionsEditTab.d.ts +1 -1
- package/dist/ui/collection_editor/import/CollectionEditorImportMapping.d.ts +7 -0
- 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 +8 -11
- package/dist/utils/collections.d.ts +6 -0
- package/package.json +23 -35
- package/src/ConfigControllerProvider.tsx +64 -66
- package/src/index.ts +1 -0
- package/src/types/collection_editor_controller.tsx +6 -5
- 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 +10 -5
- package/src/ui/EditorCollectionAction.tsx +12 -70
- package/src/ui/EditorCollectionActionStart.tsx +87 -0
- package/src/ui/EditorEntityAction.tsx +51 -0
- package/src/ui/HomePageEditorCollectionAction.tsx +21 -14
- package/src/ui/NewCollectionButton.tsx +1 -1
- package/src/ui/NewCollectionCard.tsx +3 -3
- package/src/ui/PropertyAddColumnComponent.tsx +11 -6
- package/src/ui/collection_editor/CollectionDetailsForm.tsx +157 -50
- package/src/ui/collection_editor/CollectionEditorDialog.tsx +117 -37
- package/src/ui/collection_editor/CollectionEditorWelcomeView.tsx +23 -32
- package/src/ui/collection_editor/CollectionPropertiesEditorForm.tsx +46 -41
- 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 +60 -28
- package/src/ui/collection_editor/LayoutModeSwitch.tsx +54 -0
- package/src/ui/collection_editor/PropertyEditView.tsx +264 -78
- package/src/ui/collection_editor/PropertyFieldPreview.tsx +8 -10
- package/src/ui/collection_editor/PropertyTree.tsx +184 -138
- package/src/ui/collection_editor/SubcollectionsEditTab.tsx +26 -19
- package/src/ui/collection_editor/UnsavedChangesDialog.tsx +9 -7
- package/src/ui/collection_editor/import/CollectionEditorImportDataPreview.tsx +33 -9
- package/src/ui/collection_editor/import/CollectionEditorImportMapping.tsx +42 -9
- package/src/ui/collection_editor/properties/BlockPropertyField.tsx +32 -20
- package/src/ui/collection_editor/properties/DateTimePropertyField.tsx +54 -47
- package/src/ui/collection_editor/properties/EnumPropertyField.tsx +3 -1
- package/src/ui/collection_editor/properties/MapPropertyField.tsx +8 -7
- 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/RepeatPropertyField.tsx +0 -1
- package/src/ui/collection_editor/properties/StoragePropertyField.tsx +34 -19
- package/src/ui/collection_editor/properties/StringPropertyField.tsx +1 -10
- 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 +1 -0
- package/src/ui/collection_editor/utils/update_property_for_widget.ts +9 -0
- package/src/useCollectionEditorPlugin.tsx +38 -32
- package/src/utils/collections.ts +46 -0
- package/dist/ui/RootCollectionSuggestions.d.ts +0 -3
- package/dist/ui/collection_editor/PropertySelectItem.d.ts +0 -8
- package/src/ui/RootCollectionSuggestions.tsx +0 -63
- package/src/ui/collection_editor/PropertySelectItem.tsx +0 -32
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import 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
|
-
|
|
19
|
+
AutorenewIcon,
|
|
20
20
|
Button,
|
|
21
21
|
CircularProgress,
|
|
22
|
-
|
|
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
|
|
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) => {
|
|
@@ -158,20 +159,20 @@ export function CollectionPropertiesEditorForm({
|
|
|
158
159
|
}
|
|
159
160
|
: undefined;
|
|
160
161
|
|
|
161
|
-
const getCurrentPropertiesOrder =
|
|
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
|
-
}
|
|
165
|
+
};
|
|
165
166
|
|
|
166
|
-
const updatePropertiesOrder =
|
|
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
|
-
}
|
|
173
|
+
};
|
|
173
174
|
|
|
174
|
-
const deleteProperty =
|
|
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
|
-
|
|
183
|
-
|
|
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
|
-
}
|
|
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);
|
|
@@ -273,7 +270,7 @@ export function CollectionPropertiesEditorForm({
|
|
|
273
270
|
|
|
274
271
|
};
|
|
275
272
|
|
|
276
|
-
const onPropertyErrorInternal =
|
|
273
|
+
const onPropertyErrorInternal = (id: string, namespace?: string, error?: Record<string, any>) => {
|
|
277
274
|
const propertyPath = id ? getFullId(id, namespace) : undefined;
|
|
278
275
|
console.debug("onPropertyErrorInternal", {
|
|
279
276
|
id,
|
|
@@ -286,7 +283,7 @@ export function CollectionPropertiesEditorForm({
|
|
|
286
283
|
onPropertyError(id, namespace, hasError ? error : undefined);
|
|
287
284
|
setFieldError(idToPropertiesPath(propertyPath), hasError ? "Property error" : undefined);
|
|
288
285
|
}
|
|
289
|
-
}
|
|
286
|
+
}
|
|
290
287
|
|
|
291
288
|
const closePropertyDialog = () => {
|
|
292
289
|
setSelectedPropertyIndex(undefined);
|
|
@@ -301,9 +298,9 @@ export function CollectionPropertiesEditorForm({
|
|
|
301
298
|
? values.propertiesOrder
|
|
302
299
|
: Object.keys(values.properties)) as string[];
|
|
303
300
|
|
|
304
|
-
const owner = useMemo(() => values.ownerId ? getUser(values.ownerId) : null, [getUser, values.ownerId]);
|
|
301
|
+
const owner = useMemo(() => values.ownerId && getUser ? getUser(values.ownerId) : null, [getUser, values.ownerId]);
|
|
305
302
|
|
|
306
|
-
const onPropertyClick =
|
|
303
|
+
const onPropertyClick = (propertyKey: string, namespace?: string) => {
|
|
307
304
|
console.debug("CollectionEditor: onPropertyClick", {
|
|
308
305
|
propertyKey,
|
|
309
306
|
namespace
|
|
@@ -311,11 +308,12 @@ export function CollectionPropertiesEditorForm({
|
|
|
311
308
|
setSelectedPropertyIndex(usedPropertiesOrder.indexOf(propertyKey));
|
|
312
309
|
setSelectedPropertyKey(propertyKey);
|
|
313
310
|
setSelectedPropertyNamespace(namespace);
|
|
314
|
-
}
|
|
311
|
+
};
|
|
315
312
|
|
|
316
313
|
const body = (
|
|
317
|
-
<div className={"grid grid-cols-12 gap-2 h-full bg-
|
|
318
|
-
<div className={
|
|
314
|
+
<div className={"grid grid-cols-12 gap-2 h-full bg-white dark:bg-surface-950"}>
|
|
315
|
+
<div className={cls(
|
|
316
|
+
"bg-surface-50 dark:bg-surface-900",
|
|
319
317
|
"p-4 md:p-8 pb-20 md:pb-20",
|
|
320
318
|
"col-span-12 lg:col-span-5 h-full overflow-auto",
|
|
321
319
|
!asDialog && "border-r " + defaultBorderMixin
|
|
@@ -349,7 +347,8 @@ export function CollectionPropertiesEditorForm({
|
|
|
349
347
|
</div>}
|
|
350
348
|
|
|
351
349
|
<div className="ml-1 mt-2 flex flex-row gap-2">
|
|
352
|
-
<Tooltip title={"Get the code for this collection"}
|
|
350
|
+
<Tooltip title={"Get the code for this collection"}
|
|
351
|
+
asChild={true}>
|
|
353
352
|
<IconButton
|
|
354
353
|
variant={"filled"}
|
|
355
354
|
disabled={inferringProperties}
|
|
@@ -357,17 +356,20 @@ export function CollectionPropertiesEditorForm({
|
|
|
357
356
|
<CodeIcon/>
|
|
358
357
|
</IconButton>
|
|
359
358
|
</Tooltip>
|
|
360
|
-
{inferPropertiesFromData && <Tooltip title={"Add new properties based on data"}
|
|
359
|
+
{inferPropertiesFromData && <Tooltip title={"Add new properties based on data"}
|
|
360
|
+
asChild={true}>
|
|
361
361
|
<IconButton
|
|
362
362
|
variant={"filled"}
|
|
363
363
|
disabled={inferringProperties}
|
|
364
364
|
onClick={inferPropertiesFromData}>
|
|
365
|
-
{inferringProperties ? <CircularProgress size={"small"}/> : <
|
|
365
|
+
{inferringProperties ? <CircularProgress size={"small"}/> : <AutorenewIcon/>}
|
|
366
366
|
</IconButton>
|
|
367
367
|
</Tooltip>}
|
|
368
|
-
<Tooltip title={"Add new property"}
|
|
368
|
+
<Tooltip title={"Add new property"}
|
|
369
|
+
asChild={true}>
|
|
369
370
|
<Button
|
|
370
371
|
variant={"outlined"}
|
|
372
|
+
color={"primary"}
|
|
371
373
|
onClick={() => setNewPropertyDialogOpen(true)}>
|
|
372
374
|
<AddIcon/>
|
|
373
375
|
</Button>
|
|
@@ -402,8 +404,8 @@ export function CollectionPropertiesEditorForm({
|
|
|
402
404
|
|
|
403
405
|
{!asDialog &&
|
|
404
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"}>
|
|
405
|
-
<
|
|
406
|
-
className="sticky top-8
|
|
407
|
+
<div
|
|
408
|
+
className="sticky top-8 min-h-full w-full flex flex-col justify-center">
|
|
407
409
|
|
|
408
410
|
{selectedPropertyFullId &&
|
|
409
411
|
selectedProperty &&
|
|
@@ -436,6 +438,7 @@ export function CollectionPropertiesEditorForm({
|
|
|
436
438
|
: "Select a property to edit it"}
|
|
437
439
|
</Typography>
|
|
438
440
|
<Button variant={"outlined"}
|
|
441
|
+
color={"primary"}
|
|
439
442
|
onClick={() => setNewPropertyDialogOpen(true)}
|
|
440
443
|
>
|
|
441
444
|
<AddIcon/>
|
|
@@ -447,7 +450,7 @@ export function CollectionPropertiesEditorForm({
|
|
|
447
450
|
<Typography variant={"label"} className="flex items-center justify-center">
|
|
448
451
|
{"This property is defined as a property builder in code"}
|
|
449
452
|
</Typography>}
|
|
450
|
-
</
|
|
453
|
+
</div>
|
|
451
454
|
</div>}
|
|
452
455
|
|
|
453
456
|
{asDialog && <PropertyFormDialog
|
|
@@ -469,6 +472,7 @@ export function CollectionPropertiesEditorForm({
|
|
|
469
472
|
getData={getData}
|
|
470
473
|
propertyConfigs={propertyConfigs}
|
|
471
474
|
collectionEditable={collectionEditable}
|
|
475
|
+
onCancel={closePropertyDialog}
|
|
472
476
|
onOkClicked={asDialog
|
|
473
477
|
? closePropertyDialog
|
|
474
478
|
: undefined
|
|
@@ -496,11 +500,12 @@ export function CollectionPropertiesEditorForm({
|
|
|
496
500
|
collectionEditable={collectionEditable}
|
|
497
501
|
existingPropertyKeys={values.propertiesOrder as string[]}/>
|
|
498
502
|
|
|
499
|
-
<
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
503
|
+
<ErrorBoundary>
|
|
504
|
+
<GetCodeDialog
|
|
505
|
+
collection={values}
|
|
506
|
+
open={codeDialogOpen}
|
|
507
|
+
onOpenChange={setCodeDialogOpen}/>
|
|
508
|
+
</ErrorBoundary>
|
|
504
509
|
</>
|
|
505
510
|
);
|
|
506
511
|
}
|
|
@@ -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>
|