@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.
- 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 +11093 -4800
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +11750 -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/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 +8 -11
- package/dist/utils/collections.d.ts +6 -0
- package/dist/utils/entities.d.ts +1 -1
- package/package.json +25 -37
- 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 +170 -50
- package/src/ui/collection_editor/CollectionEditorDialog.tsx +119 -39
- package/src/ui/collection_editor/CollectionEditorWelcomeView.tsx +24 -33
- package/src/ui/collection_editor/CollectionPropertiesEditorForm.tsx +46 -49
- 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 +269 -81
- package/src/ui/collection_editor/PropertyFieldPreview.tsx +12 -14
- 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 +41 -9
- package/src/ui/collection_editor/import/CollectionEditorImportMapping.tsx +43 -10
- package/src/ui/collection_editor/import/clean_import_data.ts +1 -1
- 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 +165 -20
- 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 +3 -3
- package/src/ui/collection_editor/templates/blog_template.ts +1 -4
- 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 +38 -32
- package/src/utils/collections.ts +45 -0
- package/src/utils/entities.ts +6 -9
- 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);
|
|
@@ -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 =
|
|
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 =
|
|
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
|
-
}
|
|
303
|
+
};
|
|
315
304
|
|
|
316
305
|
const body = (
|
|
317
|
-
<div className={"grid grid-cols-12 gap-2 h-full bg-
|
|
318
|
-
<div className={
|
|
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"}/> : <
|
|
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
|
-
<
|
|
406
|
-
className="sticky top-8
|
|
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
|
-
</
|
|
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
|
-
<
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
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({
|
|
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>
|