@firecms/core 3.0.0-beta.14 → 3.0.0-beta.15
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/dist/components/EntityCollectionTable/EntityCollectionRowActions.d.ts +2 -1
- package/dist/components/HomePage/DefaultHomePage.d.ts +2 -15
- package/dist/components/HomePage/HomePageDnD.d.ts +76 -0
- package/dist/components/HomePage/NavigationCard.d.ts +3 -1
- package/dist/components/HomePage/NavigationCardBinding.d.ts +3 -2
- package/dist/components/HomePage/NavigationGroup.d.ts +7 -1
- package/dist/components/HomePage/RenameGroupDialog.d.ts +9 -0
- package/dist/core/EntityEditView.d.ts +3 -0
- package/dist/core/EntityEditViewFormActions.d.ts +1 -1
- package/dist/core/field_configs.d.ts +1 -1
- package/dist/form/EntityForm.d.ts +2 -1
- package/dist/form/EntityFormActions.d.ts +6 -2
- package/dist/form/field_bindings/ReferenceAsStringFieldBinding.d.ts +9 -0
- package/dist/form/index.d.ts +1 -0
- package/dist/hooks/useBuildNavigationController.d.ts +51 -2
- package/dist/index.es.js +2184 -1052
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +2181 -1049
- package/dist/index.umd.js.map +1 -1
- package/dist/types/analytics.d.ts +1 -1
- package/dist/types/collections.d.ts +8 -2
- package/dist/types/customization_controller.d.ts +8 -0
- package/dist/types/entity_actions.d.ts +46 -6
- package/dist/types/firecms.d.ts +8 -0
- package/dist/types/navigation.d.ts +21 -5
- package/dist/types/plugins.d.ts +20 -1
- package/dist/types/properties.d.ts +7 -0
- package/dist/types/property_config.d.ts +1 -1
- package/dist/types/side_entity_controller.d.ts +4 -0
- package/dist/util/icons.d.ts +2 -2
- package/dist/util/navigation_from_path.d.ts +4 -0
- package/dist/util/resolutions.d.ts +2 -1
- package/package.json +5 -5
- package/src/components/ConfirmationDialog.tsx +1 -0
- package/src/components/EntityCollectionTable/EntityCollectionRowActions.tsx +6 -0
- package/src/components/EntityCollectionTable/PropertyTableCell.tsx +25 -3
- package/src/components/EntityCollectionTable/internal/CollectionTableToolbar.tsx +2 -2
- package/src/components/EntityCollectionView/EntityCollectionView.tsx +7 -4
- package/src/components/EntityCollectionView/EntityCollectionViewActions.tsx +3 -2
- package/src/components/FireCMSLogo.tsx +7 -51
- package/src/components/HomePage/DefaultHomePage.tsx +491 -157
- package/src/components/HomePage/FavouritesView.tsx +3 -3
- package/src/components/HomePage/HomePageDnD.tsx +599 -0
- package/src/components/HomePage/NavigationCard.tsx +47 -38
- package/src/components/HomePage/NavigationCardBinding.tsx +10 -6
- package/src/components/HomePage/NavigationGroup.tsx +63 -29
- package/src/components/HomePage/RenameGroupDialog.tsx +117 -0
- package/src/components/UnsavedChangesDialog.tsx +6 -2
- package/src/components/common/default_entity_actions.tsx +25 -9
- package/src/components/common/useDataSourceTableController.tsx +2 -2
- package/src/core/DefaultDrawer.tsx +8 -8
- package/src/core/DrawerNavigationItem.tsx +1 -1
- package/src/core/EntityEditView.tsx +41 -6
- package/src/core/EntityEditViewFormActions.tsx +154 -29
- package/src/core/EntitySidePanel.tsx +5 -2
- package/src/core/FireCMS.tsx +2 -0
- package/src/core/field_configs.tsx +15 -1
- package/src/form/EntityForm.tsx +36 -4
- package/src/form/EntityFormActions.tsx +51 -9
- package/src/form/components/StorageItemPreview.tsx +1 -1
- package/src/form/components/StorageUploadProgress.tsx +3 -3
- package/src/form/field_bindings/MarkdownEditorFieldBinding.tsx +4 -2
- package/src/form/field_bindings/ReferenceAsStringFieldBinding.tsx +135 -0
- package/src/form/field_bindings/RepeatFieldBinding.tsx +0 -1
- package/src/form/field_bindings/StorageUploadFieldBinding.tsx +12 -17
- package/src/form/index.tsx +1 -0
- package/src/hooks/useBuildNavigationController.tsx +273 -84
- package/src/internal/useBuildSideEntityController.tsx +7 -4
- package/src/preview/PropertyPreview.tsx +14 -0
- package/src/routes/FireCMSRoute.tsx +3 -3
- package/src/types/analytics.ts +3 -0
- package/src/types/collections.ts +8 -2
- package/src/types/customization_controller.tsx +9 -0
- package/src/types/entity_actions.tsx +57 -6
- package/src/types/firecms.tsx +9 -0
- package/src/types/navigation.ts +28 -6
- package/src/types/plugins.tsx +24 -1
- package/src/types/properties.ts +8 -0
- package/src/types/property_config.tsx +1 -0
- package/src/types/side_entity_controller.tsx +5 -0
- package/src/util/icons.tsx +22 -7
- package/src/util/join_collections.ts +3 -1
- package/src/util/navigation_from_path.ts +15 -5
- package/src/util/navigation_utils.ts +2 -2
- package/src/util/resolutions.ts +13 -1
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ArrowForwardIcon, Card, cls, Markdown, Typography, } from "@firecms/ui";
|
|
2
|
+
import React from "react"; // Import React
|
|
2
3
|
|
|
3
4
|
export type NavigationCardProps = {
|
|
4
5
|
name: string,
|
|
@@ -6,64 +7,72 @@ export type NavigationCardProps = {
|
|
|
6
7
|
actions: React.ReactNode;
|
|
7
8
|
icon: React.ReactNode;
|
|
8
9
|
onClick?: () => void,
|
|
10
|
+
shrink?: boolean
|
|
9
11
|
};
|
|
10
12
|
|
|
11
|
-
|
|
13
|
+
// Wrap the component with React.memo
|
|
14
|
+
export const NavigationCard = React.memo(function NavigationCard({
|
|
12
15
|
name,
|
|
13
16
|
description,
|
|
14
17
|
icon,
|
|
15
18
|
actions,
|
|
16
19
|
onClick,
|
|
20
|
+
shrink
|
|
17
21
|
}: NavigationCardProps) {
|
|
18
22
|
|
|
19
|
-
return (
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
23
|
+
return (
|
|
24
|
+
<Card
|
|
25
|
+
className={cls(
|
|
26
|
+
"h-full p-4 cursor-pointer min-h-[230px] transition-all duration-200 ease-in-out",
|
|
27
|
+
shrink && "w-full max-w-full min-h-0 scale-75"
|
|
28
|
+
)}
|
|
29
|
+
onClick={() => {
|
|
30
|
+
onClick?.();
|
|
31
|
+
}}
|
|
32
|
+
>
|
|
33
|
+
|
|
34
|
+
<div className="flex flex-col items-start h-full">
|
|
35
|
+
<div
|
|
36
|
+
className="flex-grow w-full">
|
|
24
37
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
className="flex-grow w-full">
|
|
38
|
+
<div
|
|
39
|
+
className="h-10 flex items-center w-full justify-between text-surface-300 dark:text-surface-600">
|
|
28
40
|
|
|
29
|
-
|
|
30
|
-
className="h-10 flex items-center w-full justify-between text-surface-300 dark:text-surface-600">
|
|
41
|
+
{icon}
|
|
31
42
|
|
|
32
|
-
|
|
43
|
+
<div
|
|
44
|
+
className="flex items-center gap-1"
|
|
45
|
+
onClick={(event: React.MouseEvent) => {
|
|
46
|
+
event.preventDefault();
|
|
47
|
+
event.stopPropagation();
|
|
48
|
+
}}>
|
|
33
49
|
|
|
34
|
-
|
|
35
|
-
className="flex items-center gap-1"
|
|
36
|
-
onClick={(event: React.MouseEvent) => {
|
|
37
|
-
event.preventDefault();
|
|
38
|
-
event.stopPropagation();
|
|
39
|
-
}}>
|
|
50
|
+
{actions}
|
|
40
51
|
|
|
41
|
-
|
|
52
|
+
</div>
|
|
42
53
|
|
|
43
54
|
</div>
|
|
44
55
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
{name}
|
|
50
|
-
</Typography>
|
|
56
|
+
<Typography gutterBottom variant="h5"
|
|
57
|
+
component="h2">
|
|
58
|
+
{name}
|
|
59
|
+
</Typography>
|
|
51
60
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
61
|
+
{description && <Typography variant="body2"
|
|
62
|
+
color="secondary"
|
|
63
|
+
component="div">
|
|
64
|
+
<Markdown source={description} size={"small"}/>
|
|
65
|
+
</Typography>}
|
|
66
|
+
</div>
|
|
58
67
|
|
|
59
|
-
|
|
68
|
+
<div style={{ alignSelf: "flex-end" }}>
|
|
60
69
|
|
|
61
|
-
|
|
62
|
-
|
|
70
|
+
<div className={"p-4"}>
|
|
71
|
+
<ArrowForwardIcon className="text-primary"/>
|
|
72
|
+
</div>
|
|
63
73
|
</div>
|
|
64
|
-
</div>
|
|
65
74
|
|
|
66
|
-
|
|
75
|
+
</div>
|
|
67
76
|
|
|
68
|
-
|
|
69
|
-
}
|
|
77
|
+
</Card>)
|
|
78
|
+
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useNavigate } from "react-router-dom";
|
|
2
2
|
|
|
3
3
|
import { useCustomizationController, useFireCMSContext } from "../../hooks";
|
|
4
|
-
import {
|
|
4
|
+
import { NavigationEntry, PluginHomePageActionsProps } from "../../types";
|
|
5
5
|
import { IconForView } from "../../util";
|
|
6
6
|
import { useUserConfigurationPersistence } from "../../hooks/useUserConfigurationPersistence";
|
|
7
7
|
import { IconButton, StarIcon } from "@firecms/ui";
|
|
@@ -30,9 +30,11 @@ export function NavigationCardBinding({
|
|
|
30
30
|
name,
|
|
31
31
|
description,
|
|
32
32
|
onClick,
|
|
33
|
-
type
|
|
34
|
-
|
|
35
|
-
|
|
33
|
+
type,
|
|
34
|
+
shrink
|
|
35
|
+
}: NavigationEntry & {
|
|
36
|
+
onClick?: () => void,
|
|
37
|
+
shrink?: boolean // <-- add shrink prop type
|
|
36
38
|
}) {
|
|
37
39
|
|
|
38
40
|
const userConfigurationPersistence = useUserConfigurationPersistence();
|
|
@@ -93,7 +95,7 @@ export function NavigationCardBinding({
|
|
|
93
95
|
if (type === "admin") {
|
|
94
96
|
return <SmallNavigationCard icon={collectionIcon}
|
|
95
97
|
name={name}
|
|
96
|
-
url={url}
|
|
98
|
+
url={url}/>;
|
|
97
99
|
}
|
|
98
100
|
|
|
99
101
|
return <NavigationCard
|
|
@@ -109,5 +111,7 @@ export function NavigationCardBinding({
|
|
|
109
111
|
[path, ...(userConfigurationPersistence.recentlyVisitedPaths ?? []).filter(p => p !== path)]
|
|
110
112
|
);
|
|
111
113
|
}
|
|
112
|
-
}}
|
|
114
|
+
}}
|
|
115
|
+
shrink={shrink}
|
|
116
|
+
/>;
|
|
113
117
|
}
|
|
@@ -1,39 +1,73 @@
|
|
|
1
|
-
import { PropsWithChildren } from "react";
|
|
2
|
-
import {
|
|
3
|
-
import { ExpandablePanel, Typography } from "@firecms/ui";
|
|
1
|
+
import React, { PropsWithChildren, useState } from "react";
|
|
2
|
+
import { cls, EditIcon, IconButton, Typography } from "@firecms/ui";
|
|
4
3
|
|
|
5
4
|
export function NavigationGroup({
|
|
6
5
|
children,
|
|
7
|
-
group
|
|
6
|
+
group,
|
|
7
|
+
minimised,
|
|
8
|
+
isPreview,
|
|
9
|
+
isPotentialCardDropTarget,
|
|
10
|
+
onEditGroup, // New prop to handle editing
|
|
11
|
+
dndDisabled // New prop to disable editing when D&D is off
|
|
8
12
|
}: PropsWithChildren<{
|
|
9
|
-
group: string | undefined
|
|
13
|
+
group: string | undefined,
|
|
14
|
+
minimised?: boolean,
|
|
15
|
+
isPreview?: boolean,
|
|
16
|
+
isPotentialCardDropTarget?: boolean,
|
|
17
|
+
onEditGroup?: (groupName: string) => void; // Callback to open dialog
|
|
18
|
+
dndDisabled?: boolean; // Added dndDisabled prop
|
|
10
19
|
}>) {
|
|
11
|
-
const userConfigurationPersistence = useUserConfigurationPersistence();
|
|
12
|
-
return (
|
|
13
|
-
<ExpandablePanel
|
|
14
|
-
invisible={true}
|
|
15
|
-
titleClassName={"font-medium text-sm text-surface-600 dark:text-surface-400"}
|
|
16
|
-
innerClassName={"py-4"}
|
|
17
|
-
initiallyExpanded={!(userConfigurationPersistence?.collapsedGroups ?? []).includes(group ?? "ungrouped")}
|
|
18
|
-
onExpandedChange={expanded => {
|
|
19
|
-
if (userConfigurationPersistence) {
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
{group?.toUpperCase() ?? "Views".toUpperCase()}
|
|
32
|
-
</Typography>}>
|
|
21
|
+
const [isHovered, setIsHovered] = useState(false);
|
|
22
|
+
const currentGroupName = group ?? "Views";
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<div className={cls(
|
|
26
|
+
!isPotentialCardDropTarget ? "my-10" : "my-6",
|
|
27
|
+
"transition-all duration-200 ease-in-out"
|
|
28
|
+
)}
|
|
29
|
+
>
|
|
30
|
+
<div className={`flex items-center ${isPreview ? "px-1 py-0.5 m-0" : "ml-3.5 mt-6"} `}
|
|
33
31
|
|
|
34
|
-
|
|
35
|
-
|
|
32
|
+
onMouseEnter={() => setIsHovered(true)}
|
|
33
|
+
onMouseLeave={() => setIsHovered(false)}>
|
|
34
|
+
<Typography
|
|
35
|
+
variant={isPreview ? "body2" : "caption"}
|
|
36
|
+
component={"h2"}
|
|
37
|
+
color="secondary"
|
|
38
|
+
// Minimal padding and no margin for preview title
|
|
39
|
+
className={`${isPreview ? "px-1 py-0.5" : "ml-3.5"} font-medium uppercase text-sm text-surface-600 dark:text-surface-400`}
|
|
40
|
+
>
|
|
41
|
+
{currentGroupName}
|
|
42
|
+
</Typography>
|
|
43
|
+
{!isPreview && onEditGroup && !dndDisabled && (
|
|
44
|
+
<IconButton
|
|
45
|
+
size="smallest"
|
|
46
|
+
onClick={(e) => {
|
|
47
|
+
e.stopPropagation(); // Prevent other click events
|
|
48
|
+
onEditGroup(currentGroupName);
|
|
49
|
+
}}
|
|
50
|
+
className={cls("ml-2 ", isHovered ? "opacity-100" : "opacity-0", "transition-opacity duration-100")}
|
|
51
|
+
>
|
|
52
|
+
<EditIcon size="smallest"/>
|
|
53
|
+
</IconButton>
|
|
54
|
+
)}
|
|
36
55
|
</div>
|
|
37
|
-
|
|
56
|
+
|
|
57
|
+
{isPreview ? (
|
|
58
|
+
children
|
|
59
|
+
) : minimised ? (
|
|
60
|
+
// For minimised view in the main list
|
|
61
|
+
<div className={cls("mt-4 p-8 bg-surface-accent-200 dark:bg-surface-accent-800 rounded-lg")}
|
|
62
|
+
style={{ minHeight: "50px" }}>
|
|
63
|
+
</div>
|
|
64
|
+
) : (
|
|
65
|
+
// If highlighted, the parent div already has padding, so children (NavigationGroupDroppable) don't need extra margin top as much.
|
|
66
|
+
// The inner content of NavigationGroupDroppable will define its own padding if needed when active.
|
|
67
|
+
<div className={cls("mt-4", !minimised ? "pt-0" : "")}>
|
|
68
|
+
{children}
|
|
69
|
+
</div>
|
|
70
|
+
)}
|
|
71
|
+
</div>
|
|
38
72
|
);
|
|
39
73
|
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import React, { useState, useEffect, useRef } from "react";
|
|
2
|
+
import { Button, Dialog, DialogActions, DialogContent, DialogTitle, TextField } from "@firecms/ui";
|
|
3
|
+
|
|
4
|
+
interface RenameGroupDialogProps {
|
|
5
|
+
open: boolean;
|
|
6
|
+
initialName: string;
|
|
7
|
+
existingGroupNames: string[]; // Names of other existing groups to check for duplicates
|
|
8
|
+
onClose: () => void;
|
|
9
|
+
onRename: (newName: string) => void;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function RenameGroupDialog({ open, initialName, existingGroupNames, onClose, onRename }: RenameGroupDialogProps) {
|
|
13
|
+
const [name, setName] = useState(initialName);
|
|
14
|
+
const [error, setError] = useState<string | null>(null);
|
|
15
|
+
const inputRef = useRef<HTMLInputElement | HTMLTextAreaElement | null>(null); // Create a ref for the input
|
|
16
|
+
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
if (open) {
|
|
19
|
+
setName(initialName);
|
|
20
|
+
setError(null);
|
|
21
|
+
// Focus and select text when dialog opens
|
|
22
|
+
setTimeout(() => { // setTimeout to ensure the input is rendered and focusable
|
|
23
|
+
if (inputRef.current) {
|
|
24
|
+
inputRef.current.focus();
|
|
25
|
+
inputRef.current.select();
|
|
26
|
+
}
|
|
27
|
+
}, 100);
|
|
28
|
+
}
|
|
29
|
+
}, [initialName, open]);
|
|
30
|
+
|
|
31
|
+
const handleNameChange = (event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
|
|
32
|
+
const newName = event.target.value;
|
|
33
|
+
setName(newName);
|
|
34
|
+
if (!newName.trim()) {
|
|
35
|
+
setError("Group name cannot be empty.");
|
|
36
|
+
} else if (existingGroupNames.includes(newName.trim())) {
|
|
37
|
+
setError("This group name already exists.");
|
|
38
|
+
} else {
|
|
39
|
+
setError(null);
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const handleSave = () => {
|
|
44
|
+
const trimmedName = name.trim();
|
|
45
|
+
if (!trimmedName) {
|
|
46
|
+
setError("Group name cannot be empty.");
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
if (existingGroupNames.includes(trimmedName)) {
|
|
50
|
+
setError("This group name already exists.");
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
if (!error) {
|
|
54
|
+
onRename(trimmedName);
|
|
55
|
+
onClose();
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const handleKeyDown = (event: React.KeyboardEvent<HTMLInputElement | HTMLTextAreaElement>) => {
|
|
60
|
+
if (event.key === "Enter") {
|
|
61
|
+
event.preventDefault(); // Prevent default form submission behavior
|
|
62
|
+
const trimmedName = name.trim();
|
|
63
|
+
// We need to check the error state directly as well,
|
|
64
|
+
// because the error state might not have updated if the user types and immediately hits enter.
|
|
65
|
+
let currentError = null;
|
|
66
|
+
if (!trimmedName) {
|
|
67
|
+
currentError = "Group name cannot be empty.";
|
|
68
|
+
} else if (existingGroupNames.includes(trimmedName)) {
|
|
69
|
+
currentError = "This group name already exists.";
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (!currentError && trimmedName) {
|
|
73
|
+
handleSave();
|
|
74
|
+
} else if (currentError) {
|
|
75
|
+
setError(currentError); // Ensure error is displayed if trying to submit with Enter
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const handleClose = () => {
|
|
81
|
+
setName(initialName);
|
|
82
|
+
setError(null);
|
|
83
|
+
onClose();
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
if (!open) return null;
|
|
87
|
+
|
|
88
|
+
return (
|
|
89
|
+
<Dialog open={open} onOpenChange={onClose}>
|
|
90
|
+
<DialogTitle>Rename Group</DialogTitle>
|
|
91
|
+
<DialogContent>
|
|
92
|
+
<TextField
|
|
93
|
+
inputRef={inputRef} // Pass the ref to the TextField
|
|
94
|
+
label="Group Name"
|
|
95
|
+
value={name}
|
|
96
|
+
onChange={handleNameChange}
|
|
97
|
+
onKeyDown={handleKeyDown} // Added onKeyDown handler
|
|
98
|
+
error={!!error}
|
|
99
|
+
aria-describedby={error ? "group-name-error" : undefined}
|
|
100
|
+
/>
|
|
101
|
+
{error && <p id="group-name-error" style={{ display: "none" }}>{error}</p>}
|
|
102
|
+
</DialogContent>
|
|
103
|
+
<DialogActions>
|
|
104
|
+
<Button onClick={onClose}
|
|
105
|
+
color={"primary"}
|
|
106
|
+
variant="text">
|
|
107
|
+
Cancel
|
|
108
|
+
</Button>
|
|
109
|
+
<Button onClick={handleSave}
|
|
110
|
+
color={"primary"}
|
|
111
|
+
disabled={!!error || !name.trim()}>
|
|
112
|
+
Save
|
|
113
|
+
</Button>
|
|
114
|
+
</DialogActions>
|
|
115
|
+
</Dialog>
|
|
116
|
+
);
|
|
117
|
+
}
|
|
@@ -34,8 +34,12 @@ export function UnsavedChangesDialog({
|
|
|
34
34
|
|
|
35
35
|
</DialogContent>
|
|
36
36
|
<DialogActions>
|
|
37
|
-
<Button variant="text"
|
|
38
|
-
|
|
37
|
+
<Button variant="text"
|
|
38
|
+
color={"primary"}
|
|
39
|
+
onClick={handleCancel} autoFocus> Cancel </Button>
|
|
40
|
+
<Button
|
|
41
|
+
color={"primary"}
|
|
42
|
+
onClick={handleOk}> Ok </Button>
|
|
39
43
|
</DialogActions>
|
|
40
44
|
</Dialog>
|
|
41
45
|
);
|
|
@@ -5,20 +5,26 @@ import { addRecentId } from "../EntityCollectionView/utils";
|
|
|
5
5
|
import { navigateToEntity, resolveDefaultSelectedView } from "../../util";
|
|
6
6
|
|
|
7
7
|
export const editEntityAction: EntityAction = {
|
|
8
|
-
icon: <EditIcon/>,
|
|
8
|
+
icon: <EditIcon size={"small"}/>,
|
|
9
9
|
key: "edit",
|
|
10
10
|
name: "Edit",
|
|
11
11
|
collapsed: false,
|
|
12
|
+
isEnabled: ({ entity }) => Boolean(entity),
|
|
12
13
|
onClick({
|
|
13
14
|
entity,
|
|
14
15
|
collection,
|
|
15
16
|
fullPath,
|
|
17
|
+
fullIdPath,
|
|
16
18
|
context,
|
|
17
19
|
highlightEntity,
|
|
18
20
|
unhighlightEntity,
|
|
19
21
|
openEntityMode
|
|
20
22
|
}): Promise<void> {
|
|
21
23
|
|
|
24
|
+
if (!entity) {
|
|
25
|
+
throw new Error("INTERNAL: editEntityAction: Entity is undefined");
|
|
26
|
+
}
|
|
27
|
+
|
|
22
28
|
highlightEntity?.(entity);
|
|
23
29
|
|
|
24
30
|
context.analyticsController?.onAnalyticsEvent?.("entity_click", {
|
|
@@ -30,7 +36,8 @@ export const editEntityAction: EntityAction = {
|
|
|
30
36
|
addRecentId(collection.id, entity.id);
|
|
31
37
|
}
|
|
32
38
|
|
|
33
|
-
const path = collection?.collectionGroup ?
|
|
39
|
+
const path = collection?.collectionGroup ? entity.path : (fullPath ?? collection?.path ?? entity.path);
|
|
40
|
+
const newFullIdPath = collection?.collectionGroup ? collection.id : (fullIdPath ?? collection?.id ?? entity.path);
|
|
34
41
|
const defaultSelectedView = resolveDefaultSelectedView(
|
|
35
42
|
collection ? collection.defaultSelectedView : undefined,
|
|
36
43
|
{
|
|
@@ -43,7 +50,7 @@ export const editEntityAction: EntityAction = {
|
|
|
43
50
|
collection,
|
|
44
51
|
entityId: entity.id,
|
|
45
52
|
path,
|
|
46
|
-
fullIdPath:
|
|
53
|
+
fullIdPath: newFullIdPath,
|
|
47
54
|
sideEntityController: context.sideEntityController,
|
|
48
55
|
onClose: () => unhighlightEntity?.(entity),
|
|
49
56
|
navigation: context.navigation,
|
|
@@ -55,9 +62,10 @@ export const editEntityAction: EntityAction = {
|
|
|
55
62
|
}
|
|
56
63
|
|
|
57
64
|
export const copyEntityAction: EntityAction = {
|
|
58
|
-
icon: <FileCopyIcon/>,
|
|
65
|
+
icon: <FileCopyIcon size={"small"}/>,
|
|
59
66
|
name: "Copy",
|
|
60
67
|
key: "copy",
|
|
68
|
+
isEnabled: ({ entity }) => Boolean(entity),
|
|
61
69
|
onClick({
|
|
62
70
|
entity,
|
|
63
71
|
collection,
|
|
@@ -67,19 +75,23 @@ export const copyEntityAction: EntityAction = {
|
|
|
67
75
|
unhighlightEntity,
|
|
68
76
|
openEntityMode
|
|
69
77
|
}): Promise<void> {
|
|
78
|
+
if (!entity) {
|
|
79
|
+
throw new Error("INTERNAL: copyEntityAction: Entity is undefined");
|
|
80
|
+
}
|
|
70
81
|
highlightEntity?.(entity);
|
|
71
82
|
context.analyticsController?.onAnalyticsEvent?.("copy_entity_click", {
|
|
72
83
|
path: entity.path,
|
|
73
84
|
entityId: entity.id
|
|
74
85
|
});
|
|
75
86
|
|
|
76
|
-
const path = collection?.collectionGroup ? collection.
|
|
87
|
+
const path = collection?.collectionGroup ? collection.path : (fullPath ?? collection?.path ?? entity.path);
|
|
88
|
+
const fullIdPath = collection?.collectionGroup ? collection.id : (fullPath ?? collection?.id ?? entity.path);
|
|
77
89
|
navigateToEntity({
|
|
78
90
|
openEntityMode,
|
|
79
91
|
collection,
|
|
80
92
|
entityId: entity.id,
|
|
81
93
|
path,
|
|
82
|
-
fullIdPath
|
|
94
|
+
fullIdPath,
|
|
83
95
|
copy: true,
|
|
84
96
|
sideEntityController: context.sideEntityController,
|
|
85
97
|
onClose: () => unhighlightEntity?.(entity),
|
|
@@ -91,9 +103,10 @@ export const copyEntityAction: EntityAction = {
|
|
|
91
103
|
}
|
|
92
104
|
|
|
93
105
|
export const deleteEntityAction: EntityAction = {
|
|
94
|
-
icon: <DeleteIcon/>,
|
|
106
|
+
icon: <DeleteIcon size={"small"}/>,
|
|
95
107
|
name: "Delete",
|
|
96
108
|
key: "delete",
|
|
109
|
+
isEnabled: ({ entity }) => Boolean(entity),
|
|
97
110
|
onClick({
|
|
98
111
|
entity,
|
|
99
112
|
fullPath,
|
|
@@ -101,8 +114,11 @@ export const deleteEntityAction: EntityAction = {
|
|
|
101
114
|
context,
|
|
102
115
|
selectionController,
|
|
103
116
|
onCollectionChange,
|
|
104
|
-
|
|
117
|
+
navigateBack
|
|
105
118
|
}): Promise<void> {
|
|
119
|
+
if (!entity) {
|
|
120
|
+
throw new Error("INTERNAL: deleteEntityAction: Entity is undefined");
|
|
121
|
+
}
|
|
106
122
|
const { closeDialog } = context.dialogsController.open({
|
|
107
123
|
key: "delete_entity_dialog_" + entity.id,
|
|
108
124
|
Component: ({ open }) => {
|
|
@@ -120,7 +136,7 @@ export const deleteEntityAction: EntityAction = {
|
|
|
120
136
|
});
|
|
121
137
|
selectionController?.setSelectedEntities(selectionController.selectedEntities.filter(e => e.id !== entity.id));
|
|
122
138
|
onCollectionChange?.();
|
|
123
|
-
|
|
139
|
+
navigateBack?.();
|
|
124
140
|
}}
|
|
125
141
|
onClose={closeDialog}/>;
|
|
126
142
|
}
|
|
@@ -134,7 +134,7 @@ export function useDataSourceTableController<M extends Record<string, any> = any
|
|
|
134
134
|
} = parseFilterAndSort(window.location.search);
|
|
135
135
|
|
|
136
136
|
const [filterValues, setFilterValues] = React.useState<FilterValues<Extract<keyof M, string>> | undefined>(forceFilter ?? (updateUrl ? initialFilterUrl : undefined) ?? initialFilter ?? undefined);
|
|
137
|
-
const [sortBy, setSortBy] = React.useState<[Extract<keyof M, string>, "asc" | "desc"] | undefined>(initialSortUrl ?? initialSortInternal);
|
|
137
|
+
const [sortBy, setSortBy] = React.useState<[Extract<keyof M, string>, "asc" | "desc"] | undefined>((updateUrl ? initialSortUrl : undefined) ?? initialSortInternal);
|
|
138
138
|
|
|
139
139
|
useUpdateUrl(filterValues, sortBy, searchString, updateUrl);
|
|
140
140
|
|
|
@@ -334,7 +334,7 @@ function encodeFilterAndSort(filterValues?: FilterValues<string>, sortBy?: [stri
|
|
|
334
334
|
}
|
|
335
335
|
if (encodedValue !== undefined) {
|
|
336
336
|
entries[encodeURIComponent(`${key}_op`)] = encodeURIComponent(op);
|
|
337
|
-
entries[encodeURIComponent(`${key}_value`)] = encodeURIComponent(encodedValue.toString());
|
|
337
|
+
entries[encodeURIComponent(`${key}_value`)] = encodedValue ? encodeURIComponent(encodedValue.toString()) : "null";
|
|
338
338
|
}
|
|
339
339
|
}
|
|
340
340
|
});
|
|
@@ -3,7 +3,7 @@ import React, { useCallback } from "react";
|
|
|
3
3
|
import { useLargeLayout, useNavigationController } from "../hooks";
|
|
4
4
|
|
|
5
5
|
import { Link, useNavigate } from "react-router-dom";
|
|
6
|
-
import { CMSAnalyticsEvent,
|
|
6
|
+
import { CMSAnalyticsEvent, NavigationEntry, NavigationResult } from "../types";
|
|
7
7
|
import { IconForView } from "../util";
|
|
8
8
|
import { cls, IconButton, Menu, MenuItem, MoreVertIcon, Tooltip, Typography } from "@firecms/ui";
|
|
9
9
|
import { useAnalyticsController } from "../hooks/useAnalyticsController";
|
|
@@ -45,7 +45,7 @@ export function DefaultDrawer({
|
|
|
45
45
|
const {
|
|
46
46
|
navigationEntries,
|
|
47
47
|
groups
|
|
48
|
-
}:
|
|
48
|
+
}: NavigationResult = navigation.topLevelNavigation;
|
|
49
49
|
|
|
50
50
|
const adminViews = navigationEntries.filter(e => e.type === "admin") ?? [];
|
|
51
51
|
const groupsWithoutAdmin = groups.filter(g => g !== "Admin");
|
|
@@ -63,7 +63,7 @@ export function DefaultDrawer({
|
|
|
63
63
|
</div>;
|
|
64
64
|
}, [drawerOpen]);
|
|
65
65
|
|
|
66
|
-
const onClick = (view:
|
|
66
|
+
const onClick = (view: NavigationEntry) => {
|
|
67
67
|
const eventName: CMSAnalyticsEvent = view.type === "collection"
|
|
68
68
|
? "drawer_navigate_to_collection"
|
|
69
69
|
: (view.type === "view" ? "drawer_navigate_to_view" : "unmapped_event");
|
|
@@ -90,9 +90,9 @@ export function DefaultDrawer({
|
|
|
90
90
|
{buildGroupHeader(group)}
|
|
91
91
|
{Object.values(navigationEntries)
|
|
92
92
|
.filter(e => e.group === group)
|
|
93
|
-
.map((view
|
|
93
|
+
.map((view) =>
|
|
94
94
|
<DrawerNavigationItem
|
|
95
|
-
key={
|
|
95
|
+
key={view.id}
|
|
96
96
|
icon={<IconForView collectionOrView={view.collection ?? view.view}
|
|
97
97
|
size={"small"}/>}
|
|
98
98
|
tooltipsOpen={tooltipsOpen}
|
|
@@ -128,13 +128,13 @@ export function DefaultDrawer({
|
|
|
128
128
|
</div>}
|
|
129
129
|
</IconButton>}
|
|
130
130
|
>
|
|
131
|
-
{adminViews.map((entry
|
|
131
|
+
{adminViews.map((entry) =>
|
|
132
132
|
<MenuItem
|
|
133
133
|
onClick={(event) => {
|
|
134
134
|
event.preventDefault();
|
|
135
|
-
navigate(entry.
|
|
135
|
+
navigate(entry.url); // Consistent use of entry.url for navigation
|
|
136
136
|
}}
|
|
137
|
-
key={
|
|
137
|
+
key={entry.id}>
|
|
138
138
|
{<IconForView collectionOrView={entry.view}/>}
|
|
139
139
|
{entry.name}
|
|
140
140
|
</MenuItem>)}
|
|
@@ -38,7 +38,7 @@ export function DrawerNavigationItem({
|
|
|
38
38
|
"flex flex-row items-center mr-8",
|
|
39
39
|
// "transition-all ease-in-out delay-100 duration-300",
|
|
40
40
|
// drawerOpen ? "w-full" : "w-18",
|
|
41
|
-
drawerOpen ? "pl-4 h-
|
|
41
|
+
drawerOpen ? "pl-4 h-10" : "pl-4 h-9",
|
|
42
42
|
"font-semibold text-xs",
|
|
43
43
|
isActive ? "bg-surface-accent-200 bg-opacity-60 dark:bg-surface-800 dark:bg-opacity-50" : ""
|
|
44
44
|
)}
|